X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FPod%2FParser.pm;h=85551faca8896393e45ecf5096c6aefc5c63efef;hb=8e5f3f285c3a089acb6e38e5ef624a23fca2164f;hp=9bc771db53407696c1242e58f282c6e745a98fdb;hpb=e9fdc7d2ad34e80787f760dcc04e2f23a1599092;p=p5sagit%2Fp5-mst-13.2.git diff --git a/lib/Pod/Parser.pm b/lib/Pod/Parser.pm index 9bc771d..85551fa 100644 --- a/lib/Pod/Parser.pm +++ b/lib/Pod/Parser.pm @@ -1,10 +1,7 @@ ############################################################################# # Pod/Parser.pm -- package which defines a base class for parsing POD docs. # -# Based on Tom Christiansen's Pod::Text module -# (with extensive modifications). -# -# Copyright (C) 1996-1999 Tom Christiansen. All rights reserved. +# Copyright (C) 1996-2000 by Bradford Appleton. All rights reserved. # This file is part of "PodParser". PodParser is free software; # you can redistribute it and/or modify it under the same terms # as Perl itself. @@ -13,8 +10,8 @@ package Pod::Parser; use vars qw($VERSION); -$VERSION = 1.081; ## Current version of this package -require 5.004; ## requires this Perl version or later +$VERSION = 1.13; ## Current version of this package +require 5.005; ## requires this Perl version or later ############################################################################# @@ -58,9 +55,9 @@ Pod::Parser - base class for creating POD filters and translators sub interior_sequence { my ($parser, $seq_command, $seq_argument) = @_; ## Expand an interior sequence; sample actions might be: - return "*$seq_argument*" if ($seq_command = 'B'); - return "`$seq_argument'" if ($seq_command = 'C'); - return "_${seq_argument}_'" if ($seq_command = 'I'); + return "*$seq_argument*" if ($seq_command eq 'B'); + return "`$seq_argument'" if ($seq_command eq 'C'); + return "_${seq_argument}_'" if ($seq_command eq 'I'); ## ... other sequence commands and their resulting text } @@ -74,7 +71,7 @@ Pod::Parser - base class for creating POD filters and translators =head1 REQUIRES -perl5.004, Pod::InputObjects, Exporter, FileHandle, Carp +perl5.005, Pod::InputObjects, Exporter, Symbol, Carp =head1 EXPORTS @@ -145,6 +142,59 @@ For the most part, the B base class should be able to do most of the input parsing for you and leave you free to worry about how to intepret the commands and translate the result. +Note that all we have described here in this quick overview is the +simplest most straightforward use of B to do stream-based +parsing. It is also possible to use the B function +to do more sophisticated tree-based parsing. See L<"TREE-BASED PARSING">. + +=head1 PARSING OPTIONS + +A I is simply a named option of B with a +value that corresponds to a certain specified behavior. These various +behaviors of B may be enabled/disabled by setting +or unsetting one or more I using the B method. +The set of currently accepted parse-options is as follows: + +=over 3 + +=item B<-want_nonPODs> (default: unset) + +Normally (by default) B will only provide access to +the POD sections of the input. Input paragraphs that are not part +of the POD-format documentation are not made available to the caller +(not even using B). Setting this option to a +non-empty, non-zero value will allow B to see +non-POD sections of the input as well as POD sections. The B +method can be used to determine if the corresponding paragraph is a POD +paragraph, or some other input paragraph. + +=item B<-process_cut_cmd> (default: unset) + +Normally (by default) B handles the C<=cut> POD directive +by itself and does not pass it on to the caller for processing. Setting +this option to a non-empty, non-zero value will cause B to +pass the C<=cut> directive to the caller just like any other POD command +(and hence it may be processed by the B method). + +B will still interpret the C<=cut> directive to mean that +"cutting mode" has been (re)entered, but the caller will get a chance +to capture the actual C<=cut> paragraph itself for whatever purpose +it desires. + +=item B<-warnings> (default: unset) + +Normally (by default) B recognizes a bare minimum of +pod syntax errors and warnings and issues diagnostic messages +for errors, but not for warnings. (Use B to do more +thorough checking of POD syntax.) Setting this option to a non-empty, +non-zero value will cause B to issue diagnostics for +the few warnings it recognizes as well as the errors. + +=back + +Please see L<"parseopts()"> for a complete description of the interface +for the setting and unsetting of parse-options. + =cut ############################################################################# @@ -154,12 +204,17 @@ use strict; #use diagnostics; use Pod::InputObjects; use Carp; -use FileHandle; use Exporter; +BEGIN { + if ($] < 5.6) { + require Symbol; + import Symbol; + } +} @ISA = qw(Exporter); ## These "variables" are used as local "glob aliases" for performance -use vars qw(%myData @input_stack); +use vars qw(%myData %myOpts @input_stack); ############################################################################# @@ -547,18 +602,20 @@ The value returned should correspond to the new text to use in its place If the empty string is returned or an undefined value is returned, then the given C<$text> is ignored (not processed). -This method is invoked after gathering up all thelines in a paragraph +This method is invoked after gathering up all the lines in a paragraph +and after determining the cutting state of the paragraph, but before trying to further parse or interpret them. After B returns, the current cutting state (which is returned by C<$self-Ecutting()>) is examined. If it evaluates -to false then input text (including the given C<$text>) is cut (not +to true then input text (including the given C<$text>) is cut (not processed) until the next POD directive is encountered. Please note that the B method is invoked I the B method. After all (possibly preprocessed) -lines in a paragraph have been assembled together and it has been +lines in a paragraph have been assembled together and either it has been determined that the paragraph is part of the POD documentation from one -of the selected sections, then B is invoked. +of the selected sections or the C<-want_nonPODs> option is true, +then B is invoked. The base class implementation of this method returns the given text. @@ -574,8 +631,9 @@ sub preprocess_paragraph { =head1 METHODS FOR PARSING AND PROCESSING B provides several methods to process input text. These -methods typically won't need to be overridden, but subclasses may want -to invoke them to exploit their functionality. +methods typically won't need to be overridden (and in some cases they +can't be overridden), but subclasses may want to invoke them to exploit +their functionality. =cut @@ -589,7 +647,7 @@ to invoke them to exploit their functionality. This method is useful if you need to perform your own interpolation of interior sequences and can't rely upon B to expand -them in simple bottom-up order order. +them in simple bottom-up order. The parameter C<$text> is a string or block of text to be parsed for interior sequences; and the parameter C<$line_num> is the @@ -629,6 +687,31 @@ is a reference to the interior-sequence object. [I: If the B method is specified, then it is invoked according to the interface specified in L<"interior_sequence()">]. +=item B<-expand_text> =E I|I + +Normally, the parse-tree returned by B will contain a +text-string for each contiguous sequence of characters outside of an +interior-sequence. Specifying B<-expand_text> tells B to +"preprocess" every such text-string it sees by invoking the referenced +function (or named method of the parser object) and using the return value +as the preprocessed (or "expanded") result. [Note that if the result is +an interior-sequence, then it will I be expanded as specified by the +B<-expand_seq> option; Any such recursive expansion needs to be handled by +the specified callback routine.] + +If a subroutine reference was given, it is invoked as: + + &$code_ref( $parser, $text, $ptree_node ) + +and if a method-name was given, it is invoked as: + + $parser->method_name( $text, $ptree_node ) + +where C<$parser> is a reference to the parser object, C<$text> is the +text-string encountered, and C<$ptree_node> is a reference to the current +node in the parse-tree (usually an interior-sequence object or else the +top-level node of the parse-tree). + =item B<-expand_ptree> =E I|I Rather than returning a C, pass the parse-tree as an @@ -650,12 +733,6 @@ is a reference to the parse-tree object. =cut -## This global regex is used to see if the text before a '>' inside -## an interior sequence looks like '-' or '=', but not '--' or '==' -use vars qw( $ARROW_RE ); -$ARROW_RE = join('', qw{ (?: [^=]+= | [^-]+- )$ }); -#$ARROW_RE = qr/(?:[^=]+=|[^-]+-)$/; ## 5.005+ only! - sub parse_text { my $self = shift; local $_ = ''; @@ -663,15 +740,17 @@ sub parse_text { ## Get options and set any defaults my %opts = (ref $_[0]) ? %{ shift() } : (); my $expand_seq = $opts{'-expand_seq'} || undef; + my $expand_text = $opts{'-expand_text'} || undef; my $expand_ptree = $opts{'-expand_ptree'} || undef; my $text = shift; my $line = shift; my $file = $self->input_file(); - my ($cmd, $prev) = ('', ''); + my $cmd = ""; ## Convert method calls into closures, for our convenience my $xseq_sub = $expand_seq; + my $xtext_sub = $expand_text; my $xptree_sub = $expand_ptree; if (defined $expand_seq and $expand_seq eq 'interior_sequence') { ## If 'interior_sequence' is the method to use, we have to pass @@ -684,8 +763,9 @@ sub parse_text { }; } ref $xseq_sub or $xseq_sub = sub { shift()->$expand_seq(@_) }; + ref $xtext_sub or $xtext_sub = sub { shift()->$expand_text(@_) }; ref $xptree_sub or $xptree_sub = sub { shift()->$expand_ptree(@_) }; - + ## Keep track of the "current" interior sequence, and maintain a stack ## of "in progress" sequences. ## @@ -697,50 +777,85 @@ sub parse_text { ## my $seq = Pod::ParseTree->new(); my @seq_stack = ($seq); + my ($ldelim, $rdelim) = ('', ''); - ## Iterate over all sequence starts/stops, newlines, & text - ## (NOTE: split with capturing parens keeps the delimiters) + ## Iterate over all sequence starts text (NOTE: split with + ## capturing parens keeps the delimiters) $_ = $text; - for ( split /([A-Z]<|>|\n)/ ) { - ## Keep track of line count - ++$line if ($_ eq "\n"); + my @tokens = split /([A-Z]<(?:<+\s)?)/; + while ( @tokens ) { + $_ = shift @tokens; ## Look for the beginning of a sequence - if ( /^([A-Z])(<)$/ ) { + if ( /^([A-Z])(<(?:<+\s)?)$/ ) { ## Push a new sequence onto the stack of those "in-progress" + ($cmd, $ldelim) = ($1, $2); $seq = Pod::InteriorSequence->new( - -name => ($cmd = $1), - -ldelim => $2, -rdelim => '', - -file => $file, -line => $line + -name => $cmd, + -ldelim => $ldelim, -rdelim => '', + -file => $file, -line => $line ); + $ldelim =~ s/\s+$//, ($rdelim = $ldelim) =~ tr//; (@seq_stack > 1) and $seq->nested($seq_stack[-1]); push @seq_stack, $seq; } - ## Look for sequence ending (preclude '->' and '=>' inside C<...>) - elsif ( (@seq_stack > 1) and - /^>$/ and ($cmd ne 'C' or $prev !~ /$ARROW_RE/o) ) - { - ## End of current sequence, record terminating delimiter - $seq->rdelim($_); - ## Pop it off the stack of "in progress" sequences - pop @seq_stack; - ## Append result to its parent in current parse tree - $seq_stack[-1]->append($expand_seq ? &$xseq_sub($self,$seq) : $seq); - ## Remember the current cmd-name - $cmd = (@seq_stack > 1) ? $seq_stack[-1]->name : ''; + ## Look for sequence ending + elsif ( @seq_stack > 1 ) { + ## Make sure we match the right kind of closing delimiter + my ($seq_end, $post_seq) = ("", ""); + if ( ($ldelim eq '<' and /\A(.*?)(>)/s) + or /\A(.*?)(\s+$rdelim)/s ) + { + ## Found end-of-sequence, capture the interior and the + ## closing the delimiter, and put the rest back on the + ## token-list + $post_seq = substr($_, length($1) + length($2)); + ($_, $seq_end) = ($1, $2); + (length $post_seq) and unshift @tokens, $post_seq; + } + if (length) { + ## In the middle of a sequence, append this text to it, and + ## dont forget to "expand" it if that's what the caller wanted + $seq->append($expand_text ? &$xtext_sub($self,$_,$seq) : $_); + $_ .= $seq_end; + } + if (length $seq_end) { + ## End of current sequence, record terminating delimiter + $seq->rdelim($seq_end); + ## Pop it off the stack of "in progress" sequences + pop @seq_stack; + ## Append result to its parent in current parse tree + $seq_stack[-1]->append($expand_seq ? &$xseq_sub($self,$seq) + : $seq); + ## Remember the current cmd-name and left-delimiter + $cmd = (@seq_stack > 1) ? $seq_stack[-1]->name : ''; + $ldelim = (@seq_stack > 1) ? $seq_stack[-1]->ldelim : ''; + $ldelim =~ s/\s+$//, ($rdelim = $ldelim) =~ tr//; + } } - else { - ## In the middle of a sequence, append this text to it - $seq->append($_) if length; + elsif (length) { + ## In the middle of a sequence, append this text to it, and + ## dont forget to "expand" it if that's what the caller wanted + $seq->append($expand_text ? &$xtext_sub($self,$_,$seq) : $_); } - ## Remember the "current" sequence and the previously seen token - ($seq, $prev) = ( $seq_stack[-1], $_ ); + ## Keep track of line count + $line += tr/\n//; + ## Remember the "current" sequence + $seq = $seq_stack[-1]; } ## Handle unterminated sequences + my $errorsub = (@seq_stack > 1) ? $self->errorsub() : undef; while (@seq_stack > 1) { ($cmd, $file, $line) = ($seq->name, $seq->file_line); + $ldelim = $seq->ldelim; + ($rdelim = $ldelim) =~ tr//; + $rdelim =~ s/^(\S+)(\s*)$/$2$1/; pop @seq_stack; - warn "** Unterminated $cmd<...> at $file line $line\n"; + my $errmsg = "*** ERROR: unterminated ${cmd}${ldelim}...${rdelim}". + " at line $line in file $file\n"; + (ref $errorsub) and &{$errorsub}($errmsg) + or (defined $errorsub) and $self->$errorsub($errmsg) + or warn($errmsg); $seq_stack[-1]->append($expand_seq ? &$xseq_sub($self,$seq) : $seq); $seq = $seq_stack[-1]; } @@ -787,7 +902,8 @@ This method takes the text of a POD paragraph to be processed, along with its corresponding line number, and invokes the appropriate method (one of B, B, or B). -This method does I usually need to be overridden by subclasses. +For performance reasons, this method is invoked directly without any +dynamic lookup; Hence subclasses may I override it! =end __PRIVATE__ @@ -795,15 +911,21 @@ This method does I usually need to be overridden by subclasses. sub parse_paragraph { my ($self, $text, $line_num) = @_; - local *myData = $self; ## an alias to avoid deref-ing overhead + local *myData = $self; ## alias to avoid deref-ing overhead + local *myOpts = ($myData{_PARSEOPTS} ||= {}); ## get parse-options local $_; - ## This is the end of a non-empty paragraph + ## See if we want to preprocess nonPOD paragraphs as well as POD ones. + my $wantNonPods = $myOpts{'-want_nonPODs'}; + + ## Update cutting status + $myData{_CUTTING} = 0 if $text =~ /^={1,2}\S/; + + ## Perform any desired preprocessing if we wanted it this early + $wantNonPods and $text = $self->preprocess_paragraph($text, $line_num); + ## Ignore up until next POD directive if we are cutting - if ($myData{_CUTTING}) { - return unless ($text =~ /^={1,2}\S/); - $myData{_CUTTING} = 0; - } + return if $myData{_CUTTING}; ## Now we know this is block of text in a POD section! @@ -821,10 +943,13 @@ sub parse_paragraph { $self->is_selected($text) or return ($myData{_CUTTING} = 1); } - ## Perform any desired preprocessing and re-check the "cutting" state - $text = $self->preprocess_paragraph($text, $line_num); - return 1 unless ((defined $text) and (length $text)); - return 1 if ($myData{_CUTTING}); + ## If we havent already, perform any desired preprocessing and + ## then re-check the "cutting" state + unless ($wantNonPods) { + $text = $self->preprocess_paragraph($text, $line_num); + return 1 unless ((defined $text) and (length $text)); + return 1 if ($myData{_CUTTING}); + } ## Look for one of the three types of paragraphs my ($pfx, $cmd, $arg, $sep) = ('', '', '', ''); @@ -835,13 +960,12 @@ sub parse_paragraph { ## and whatever sequence of characters was used to separate them $pfx = $1; $_ = substr($text, length $pfx); - $sep = /(\s+)(?=\S)/ ? $1 : ''; - ($cmd, $text) = split(" ", $_, 2); + ($cmd, $sep, $text) = split /(\s+)/, $_, 2; ## If this is a "cut" directive then we dont need to do anything ## except return to "cutting" mode. if ($cmd eq 'cut') { $myData{_CUTTING} = 1; - return; + return unless $myOpts{'-process_cut_cmd'}; } } ## Save the attributes indicating how the command was specified. @@ -914,6 +1038,9 @@ sub parse_from_filehandle { my $self = shift; my %opts = (ref $_[0] eq 'HASH') ? %{ shift() } : (); my ($in_fh, $out_fh) = @_; + $in_fh = \*STDIN unless ($in_fh); + local *myData = $self; ## alias to avoid deref-ing overhead + local *myOpts = ($myData{_PARSEOPTS} ||= {}); ## get parse-options local $_; ## Put this stream at the top of the stack and do beginning-of-input @@ -946,9 +1073,21 @@ sub parse_from_filehandle { ++$plines; } - ## See of this line is blank and ends the current paragraph. + ## See if this line is blank and ends the current paragraph. ## If it isnt, then keep iterating until it is. - next unless (($textline =~ /^\s*$/) && (length $paragraph)); + next unless (($textline =~ /^([^\S\r\n]*)[\r\n]*$/) + && (length $paragraph)); + + ## Issue a warning about any non-empty blank lines + if (length($1) > 0 and $myOpts{'-warnings'} and ! $myData{_CUTTING}) { + my $errorsub = $self->errorsub(); + my $file = $self->input_file(); + my $errmsg = "*** WARNING: line containing nothing but whitespace". + " in paragraph at line $nlines in file $file\n"; + (ref $errorsub) and &{$errorsub}($errmsg) + or (defined $errorsub) and $self->$errorsub($errmsg) + or warn($errmsg); + } ## Now process the paragraph parse_paragraph($self, $paragraph, ($nlines - $plines) + 1); @@ -1010,7 +1149,7 @@ sub parse_from_file { my $self = shift; my %opts = (ref $_[0] eq 'HASH') ? %{ shift() } : (); my ($infile, $outfile) = @_; - my ($in_fh, $out_fh) = (undef, undef); + my ($in_fh, $out_fh) = (gensym, gensym) if ($] < 5.6); my ($close_input, $close_output) = (0, 0); local *myData = $self; local $_; @@ -1031,7 +1170,7 @@ sub parse_from_file { else { ## We have a filename, open it for reading $myData{_INFILE} = $infile; - $in_fh = FileHandle->new("< $infile") or + open($in_fh, "< $infile") or croak "Can't open $infile for reading: $!\n"; $close_input = 1; } @@ -1061,13 +1200,14 @@ sub parse_from_file { elsif (ref $outfile) { ## Must be a filehandle-ref (or else assume its a ref to an ## object that supports the common IO write operations). - $myData{_OUTFILE} = ${$outfile};; + $myData{_OUTFILE} = ${$outfile}; $out_fh = $outfile; } else { ## We have a filename, open it for writing $myData{_OUTFILE} = $outfile; - $out_fh = FileHandle->new("> $outfile") or + (-d $outfile) and croak "$outfile is a directory, not POD input!\n"; + open($out_fh, "> $outfile") or croak "Can't open $outfile for writing: $!\n"; $close_output = 1; } @@ -1095,6 +1235,35 @@ instance data fields: ##--------------------------------------------------------------------------- +=head1 B + + $parser->errorsub("method_name"); + $parser->errorsub(\&warn_user); + $parser->errorsub(sub { print STDERR, @_ }); + +Specifies the method or subroutine to use when printing error messages +about POD syntax. The supplied method/subroutine I return TRUE upon +successful printing of the message. If C is given, then the B +builtin is used to issue error messages (this is the default behavior). + + my $errorsub = $parser->errorsub() + my $errmsg = "This is an error message!\n" + (ref $errorsub) and &{$errorsub}($errmsg) + or (defined $errorsub) and $parser->$errorsub($errmsg) + or warn($errmsg); + +Returns a method name, or else a reference to the user-supplied subroutine +used to print error messages. Returns C if the B builtin +is used to issue error messages (this is the default behavior). + +=cut + +sub errorsub { + return (@_ > 1) ? ($_[0]->{_ERRORSUB} = $_[1]) : $_[0]->{_ERRORSUB}; +} + +##--------------------------------------------------------------------------- + =head1 B $boolean = $parser->cutting(); @@ -1116,6 +1285,58 @@ sub cutting { ##--------------------------------------------------------------------------- +##--------------------------------------------------------------------------- + +=head1 B + +When invoked with no additional arguments, B returns a hashtable +of all the current parsing options. + + ## See if we are parsing non-POD sections as well as POD ones + my %opts = $parser->parseopts(); + $opts{'-want_nonPODs}' and print "-want_nonPODs\n"; + +When invoked using a single string, B treats the string as the +name of a parse-option and returns its corresponding value if it exists +(returns C if it doesn't). + + ## Did we ask to see '=cut' paragraphs? + my $want_cut = $parser->parseopts('-process_cut_cmd'); + $want_cut and print "-process_cut_cmd\n"; + +When invoked with multiple arguments, B treats them as +key/value pairs and the specified parse-option names are set to the +given values. Any unspecified parse-options are unaffected. + + ## Set them back to the default + $parser->parseopts(-warnings => 0); + +When passed a single hash-ref, B uses that hash to completely +reset the existing parse-options, all previous parse-option values +are lost. + + ## Reset all options to default + $parser->parseopts( { } ); + +See L<"PARSING OPTIONS"> for more information on the name and meaning of each +parse-option currently recognized. + +=cut + +sub parseopts { + local *myData = shift; + local *myOpts = ($myData{_PARSEOPTS} ||= {}); + return %myOpts if (@_ == 0); + if (@_ == 1) { + local $_ = shift; + return ref($_) ? $myData{_PARSEOPTS} = $_ : $myOpts{$_}; + } + my @newOpts = (%myOpts, @_); + $myData{_PARSEOPTS} = { @newOpts }; +} + +##--------------------------------------------------------------------------- + =head1 B $fname = $parser->output_file(); @@ -1359,6 +1580,159 @@ sub _pop_input_stream { ############################################################################# +=head1 TREE-BASED PARSING + +If straightforward stream-based parsing wont meet your needs (as is +likely the case for tasks such as translating PODs into structured +markup languages like HTML and XML) then you may need to take the +tree-based approach. Rather than doing everything in one pass and +calling the B method to expand sequences into text, it +may be desirable to instead create a parse-tree using the B +method to return a tree-like structure which may contain an ordered +list of children (each of which may be a text-string, or a similar +tree-like structure). + +Pay special attention to L<"METHODS FOR PARSING AND PROCESSING"> and +to the objects described in L. The former describes +the gory details and parameters for how to customize and extend the +parsing behavior of B. B provides +several objects that may all be used interchangeably as parse-trees. The +most obvious one is the B object. It defines the basic +interface and functionality that all things trying to be a POD parse-tree +should do. A B is defined such that each "node" may be a +text-string, or a reference to another parse-tree. Each B +object and each B object also supports the basic +parse-tree interface. + +The B method takes a given paragraph of text, and +returns a parse-tree that contains one or more children, each of which +may be a text-string, or an InteriorSequence object. There are also +callback-options that may be passed to B to customize +the way it expands or transforms interior-sequences, as well as the +returned result. These callbacks can be used to create a parse-tree +with custom-made objects (which may or may not support the parse-tree +interface, depending on how you choose to do it). + +If you wish to turn an entire POD document into a parse-tree, that process +is fairly straightforward. The B method is the key to doing +this successfully. Every paragraph-callback (i.e. the polymorphic methods +for B, B, and B paragraphs) takes +a B object as an argument. Each paragraph object has a +B method that can be used to get or set a corresponding +parse-tree. So for each of those paragraph-callback methods, simply call +B with the options you desire, and then use the returned +parse-tree to assign to the given paragraph object. + +That gives you a parse-tree for each paragraph - so now all you need is +an ordered list of paragraphs. You can maintain that yourself as a data +element in the object/hash. The most straightforward way would be simply +to use an array-ref, with the desired set of custom "options" for each +invocation of B. Let's assume the desired option-set is +given by the hash C<%options>. Then we might do something like the +following: + + package MyPodParserTree; + + @ISA = qw( Pod::Parser ); + + ... + + sub begin_pod { + my $self = shift; + $self->{'-paragraphs'} = []; ## initialize paragraph list + } + + sub command { + my ($parser, $command, $paragraph, $line_num, $pod_para) = @_; + my $ptree = $parser->parse_text({%options}, $paragraph, ...); + $pod_para->parse_tree( $ptree ); + push @{ $self->{'-paragraphs'} }, $pod_para; + } + + sub verbatim { + my ($parser, $paragraph, $line_num, $pod_para) = @_; + push @{ $self->{'-paragraphs'} }, $pod_para; + } + + sub textblock { + my ($parser, $paragraph, $line_num, $pod_para) = @_; + my $ptree = $parser->parse_text({%options}, $paragraph, ...); + $pod_para->parse_tree( $ptree ); + push @{ $self->{'-paragraphs'} }, $pod_para; + } + + ... + + package main; + ... + my $parser = new MyPodParserTree(...); + $parser->parse_from_file(...); + my $paragraphs_ref = $parser->{'-paragraphs'}; + +Of course, in this module-author's humble opinion, I'd be more inclined to +use the existing B object than a simple array. That way +everything in it, paragraphs and sequences, all respond to the same core +interface for all parse-tree nodes. The result would look something like: + + package MyPodParserTree2; + + ... + + sub begin_pod { + my $self = shift; + $self->{'-ptree'} = new Pod::ParseTree; ## initialize parse-tree + } + + sub parse_tree { + ## convenience method to get/set the parse-tree for the entire POD + (@_ > 1) and $_[0]->{'-ptree'} = $_[1]; + return $_[0]->{'-ptree'}; + } + + sub command { + my ($parser, $command, $paragraph, $line_num, $pod_para) = @_; + my $ptree = $parser->parse_text({<>}, $paragraph, ...); + $pod_para->parse_tree( $ptree ); + $parser->parse_tree()->append( $pod_para ); + } + + sub verbatim { + my ($parser, $paragraph, $line_num, $pod_para) = @_; + $parser->parse_tree()->append( $pod_para ); + } + + sub textblock { + my ($parser, $paragraph, $line_num, $pod_para) = @_; + my $ptree = $parser->parse_text({<>}, $paragraph, ...); + $pod_para->parse_tree( $ptree ); + $parser->parse_tree()->append( $pod_para ); + } + + ... + + package main; + ... + my $parser = new MyPodParserTree2(...); + $parser->parse_from_file(...); + my $ptree = $parser->parse_tree; + ... + +Now you have the entire POD document as one great big parse-tree. You +can even use the B<-expand_seq> option to B to insert +whole different kinds of objects. Just don't expect B +to know what to do with them after that. That will need to be in your +code. Or, alternatively, you can insert any object you like so long as +it conforms to the B interface. + +One could use this to create subclasses of B and +B for specific commands (or to create your own +custom node-types in the parse-tree) and add some kind of B +method to each custom node/subclass object in the tree. Then all you'd +need to do is recursively walk the tree in the desired order, processing +the children (most likely from left to right) by formatting them if +they are text-strings, or by calling their B method if they +are objects/references. + =head1 SEE ALSO L, L