PerlPoint-Package

 view release on metacpan or  search on metacpan

lib/PerlPoint/Parser.pm  view on Meta::CPAN

#         |21.06.2003| JSTENZEL | headlines provide additional data: their numerical, full
#         |          |          | and shortcut pathes;
#         |22.06.2003| JSTENZEL | _normalizeTableRows() now supplies number of columns both
#         |          |          | in title row and the maximum value;
#         |          | JSTENZEL | new warning if the maximum columns number is detected
#         |          |          | in another line than the first table line (which is the
#         |          |          | base of normalization);
#         |10.08.2003| JSTENZEL | new helper function _semerr() to report semantic errors;
#         |          | JSTENZEL | new option -criticalSemanticErrors;
#         |14.08.2003| JSTENZEL | input filters can access the source file by a variable
#         |          |          | $main::_ifilterFile now;
#         |          | JSTENZEL | fixed an "undefined value" warning;
#         |17.08.2003| JSTENZEL | bugfix: docstream "main" was ignored like any other docstream
#         |          |          | if working in the "docstream ignore" mode;
#         |10.09.2003| JSTENZEL | definition list explanations ("texts" now have an own
#         |          |          | enveloping directive (DIRECTIVE_DPOINT_TEXT);
#         |11.09.2003| JSTENZEL | LOCALTOC added to the list of standalone tags (which are
#         |          |          | stripped of of an enveloping text paragraph if they are its
#         |          |          | only contents);
#         |05.05.2004| JSTENZEL | anchors now take the number of the page they are defined in;
#         |          | JSTENZEL | tag hooks now take an additional parameter: the number of
#         |          |          | the page the tag is used on;
#         |          | JSTENZEL | bugfix: numerical pathes were built incorrectly: when entering
#         |          |          | a new sublevel, the counter was not reset to 1;
#         |          | JSTENZEL | added anchors();
#         |11.07.2004| JSTENZEL | headlines now provide a path of absolute page numbers as well
#         |          |          | and a variable snapshot;
#         |          | JSTENZEL | a reset variable is removed now (as a side effect, it is no
#         |          |          | longer possible to build variables containing spaces only);
#         |24.07.2004| JSTENZEL | added -skipcomments;
#         |10.09.2004| JSTENZEL | bugfix: words looking like symbolic variables (but not defined
#         |          |          | as such) were restored without their braces ("{}");
#         |27.12.2004| JSTENZEL | bugfix: skipped headline levels were filled with previous
#         |          |          | headline strings of those levels;
#         |28.12.2004| JSTENZEL | text paragraphs now have their own special character, but
#         |          |          | optional: a dot;
#         |24.02.2005| JSTENZEL | acceleration: the lexer built some data very often;
#         |27.02.2005| JSTENZEL | bugfix: backslashes before variables were handled incorrectly,
#         |          |          | now variables are no longer "boosted" but handled like macros
#         |          |          | - which has a performance drawback, unfortunately ...;
#         |16.05.2005| JSTENZEL | backslashes in tag options are no longer ignored but can be
#         |          |          | used to guard characters;
#         |23.08.2005| JSTENZEL | first chapter is checked for a headline now;
# 0.39    |01.02.2003| JSTENZEL | passing directive id chain of the current chapter
#         |          |          | headline to tag hook functions now;
#         |07.03.2003| JSTENZEL | several variable patterns were used explicitly instead
#         |          |          | if the precompiled ones from %lexerPatterns;
#         |          | JSTENZEL | bugfix: guarded variables were expanded;
#         |          | JSTENZEL | now it is documented that list indentation is reset
#         |          |          | automatically by a subsequent non list paragraph;
#         |26.04.2003| JSTENZEL | added "no utf8" to avoid errors under perl 5.8;
#         |01.05.2003| JSTENZEL | adding *all* composite anchors for headlines, not only
#         |          |          | for the full path;
# 0.38    |07.06.2002| JSTENZEL | restoring doubled backslashes in filtered paragraphs,
#         |          |          | restoring ">" characters as if they were guarded;
#         |04.07.2002| JSTENZEL | simplified several array field access codes;
#         |          | JSTENZEL | bugfix: empty headlines caused an infinite loop
#         |          |          | when trailing whitespaces should be removed;
#         |          | JSTENZEL | bugfix: empty headlines caused a failure when headline
#         |          |          | anchors should be stored, skipping them now;
#         |20.08.2002| JSTENZEL | improved tag streaming: stream now contains a body hint;
#         |          | JSTENZEL | bugfix: paragraph filters restored tag bodies even if
#         |          |          | there was no body;
#         |          | JSTENZEL | old caches need to be updated - adapted compatibility hint;
#         |27.08.2002| JSTENZEL | started to use precompiled lexer patterns;
#         |31.08.2002| JSTENZEL | \INCLUDE, \EMBED and \TABLE now support the _cnd_ option,
#         |          |          | like tags defined externally;
#         |04.12.2002| JSTENZEL | bugfix in pfilter retranslation: backslash reinsertion was
#         |          |          | not performed multiply;
#         |          | JSTENZEL | pfilter retranslation: backslash reinsertion now suppressed
#         |          |          | in verbatim blocks;
#         |01.01.2003| JSTENZEL | added input filter support to \EMBED, via option "ifilter";
#         |02.01.2003| JSTENZEL | added input filter support to \INCLUDE, same interface;
# 0.37    |up to     | JSTENZEL | flagSet() now takes a list of flag names;
#         |14.04.2002| JSTENZEL | names of included files are resolved to avoid trouble
#         |          |          | with links (and to avoid error messages);
#         |          | JSTENZEL | \INCLUDE searches pathes specified in environment
#         |          |          | variable PERLPOINTLIB (like perl, shells, linkers etc.);
#         |          | JSTENZEL | if tags with finish hooks are used, a paragraph will
#         |          |          | not be cached because it becomes potentially dynamic;
#         |          | JSTENZEL | anchors defined by a cached paragraph are cached now
#         |          |          | as well - and restored after a cache hit (updated cache
#         |          |          | format);
#         |          | JSTENZEL | \INCLUDE additionally searches pathes specified in an
#         |          |          | array passed to method run() via new parameter "libpath";
#         |          | JSTENZEL | Filtered paragraphs that need a parser lookahead into
#         |          |          | the next paragraph to be completely detected could cause
#         |          |          | trouble because the reinserted result was grammatically
#         |          |          | placed before the already parsed start token of the
#         |          |          | subsequent paragraph. Fixed by introducing a virtual,
#         |          |          | empty "Word" token supplied by the lexer in such cases
#         |          |          | (look for $flags{virtualParagraphStart} and
#         |          |          | $lexerFlags{cbell}). (By the way, this outdated an
#         |          |          | earlier solution using a virtual text paragraph startup
#         |          |          | and a delayed token - this former solution caused trouble
#         |          |          | when the paragraph following the filtered one was not
#         |          |          | a pure text (so even filtered texts did not work)).
#         |          | JSTENZEL | Filtered paragraphs are no longer cached - the filter
#         |          |          | makes them dynamical. Note that for combined paragraphs
#         |          |          | like compound blocks and lists this is true for the first
#         |          |          | part only, because subsequent parts can be cached in
#         |          |          | their original form (the filter will be applied when the
#         |          |          | parts will have been combined).
#         |          | JSTENZEL | paragraph filters: added retranslation of headlines and
#         |          |          | verbatim blocks;
#         |          | JSTENZEL | passing original paragraph type to filters by new variable
#         |          |          | $main::_pfilterType;
#         |          | JSTENZEL | generalized paragraph type constant to string translation;
#         |          | JSTENZEL | lexer delays to flag the end of the document source
#         |          |          | when a paragraph filter still needs to be applied
#         |          |          | (otherwise, the parser would not request more tokens
#         |          |          | because from his point of view the source was already
#         |          |          | parsed completely, so the filtering result (and the
#         |          |          | original block) would disappear from the result - it would
#         |          |          | not be reparsed);
#         |          | JSTENZEL | empty text paragraphs are no longer made part of the stream;
#         |          | JSTENZEL | blocks were streamed with a final newline, improved;
#         |          | JSTENZEL | added headline shortcuts;
#         |          | JSTENZEL | added document stream entry points;
#         |15.04.2002| JSTENZEL | added chapter docstream hints to headline stream data;
# 0.36    |10.08.2001| JSTENZEL | the stream became a more complex data structure to

lib/PerlPoint/Parser.pm  view on Meta::CPAN

            _stateManager(STATE_COMMENT);

            # trace, if necessary
            warn "[Trace] $sourceFile, line $_[1][1]: Comment starts.\n" if $flags{trace} & TRACE_PARAGRAPHS;
           }
	],
	[#Rule 79
		 'comment', 5,
sub
#line 2722 "ppParser.yp"
{
            # back to default mode
            _stateManager(STATE_DEFAULT);

            # trace, if necessary
            warn "[Trace] $sourceFile, line $_[5][1]: Comment completed.\n" if $flags{trace} & TRACE_PARAGRAPHS;

            # reply data, if necessary
            my %hints=(nr=>++$directiveCounter);
            $flags{skipcomments} ? [[()], $_[5][1]]
                                 : [
                                    [
                                     # opener directive
                                     [\%hints, DIRECTIVE_COMMENT, DIRECTIVE_START],
                                     # the list of enclosed literals
                                     @{$_[4][0]},
                                     # final directive
                                     [\%hints, DIRECTIVE_COMMENT, DIRECTIVE_COMPLETE]
                                    ],
                                    $_[5][1]
                                   ];
           }
	],
	[#Rule 80
		 '@16-1', 0,
sub
#line 2748 "ppParser.yp"
{
                       # no mode switch necessary

                       # trace, if necessary
                       warn "[Trace] $sourceFile, line $_[1][1]: Stream entry point starts.\n" if $flags{trace} & TRACE_PARAGRAPHS;
                      }
	],
	[#Rule 81
		 'dstream_entrypoint', 4,
sub
#line 2755 "ppParser.yp"
{
                       # no mode switch necessary

                       # trace, if necessary
                       warn "[Trace] $sourceFile, line $_[5][1]: Stream entry point completed.\n" if $flags{trace} & TRACE_PARAGRAPHS;

                       # deactivate caching
                       $flags{checksummed}=0;

                       # reply data as wished
                       my $streamTitle=join('', @{$_[3][0]});
                       unless (
                                  $flags{docstreaming}==DSTREAM_IGNORE
                               or (
                                       $flags{docstreams2skip}
                                   and exists $flags{docstreams2skip}{$streamTitle}
                                  )
                              )
                         {
                          # store stream title (both globally and locally)
                          $resultStreamRef->[STREAM_DOCSTREAMS]{$streamTitle}=$flags{chapterDocstreams}{$streamTitle}=undef;

                          # special handling requested?
                          if ($flags{docstreaming}==DSTREAM_HEADLINES)
                            {
                             # make this docstream entry point a headline
                             # one level below the last real headline level
                             my %hints=(nr=>++$directiveCounter, shortcut=>'');
                             [
                              [
                               # opener directive (including headline level)
                               [\%hints, DIRECTIVE_HEADLINE, DIRECTIVE_START, $flags{headlineLevel}+1],
                               # the stream title becomes the "headline"
                               $streamTitle,
                               # final directive (including headline level again)
                               [\%hints, DIRECTIVE_HEADLINE, DIRECTIVE_COMPLETE, $flags{headlineLevel}+1]
                              ],
                              $_[5][1]
                             ];
                            }
                          # default handling
                          else
                            {
                             my %hints=(nr=>++$directiveCounter);
                             [
                              [
                               # directives
                               [\%hints, DIRECTIVE_DSTREAM_ENTRYPOINT, DIRECTIVE_START, $streamTitle],
                              ],
                              $_[5][1]
                             ];
                            }
                         }
                       else
                         {
                          # configure parser to ignore eveything till the next stream entry point or headline
                          # ... unless this is the *main* stream
                          $flags{skipInput}=2 unless $streamTitle eq 'main';

                          # we have to supply something, but it should be nothing (note that this is a *paragraph*, so reply a *string*)
                          ['', $_[5][1]];
                         }
                      }
	],
	[#Rule 82
		 '@17-1', 0,
sub
#line 2822 "ppParser.yp"
{
                # temporarily activate number detection
                push(@specialStack, $specials{number});
                $specials{number}=1;
               }
	],
	[#Rule 83
		 '@18-3', 0,
sub
#line 2828 "ppParser.yp"
{
                # restore previous number detection mode
                $specials{number}=pop(@specialStack);

                # switch to control mode
                _stateManager(STATE_CONTROL);

lib/PerlPoint/Parser.pm  view on Meta::CPAN

I<Please note> that there is a problem with line numbers if paragraphs are
restored from cache because of the behaviour of perls paragraph mode. In this
mode, the <> operator reads in any number of newlines between paragraphs but
supplies only one of them. That is why I do not get the real number of lines
in a paragraph and therefore cannot store them. To work around this, two
strategies can be used. First, do not use more than exactly one newline
between paragraphs. (This strategy is not for real life users, of course,
but in this case restored numbers would be correct.) Second, remember that
source line numbers are only interesting in error messages. If the parser
detects an error, it therefore says: error "there or later" when a cache hit
already occured. If the real number is wished the parser could be reinvoked
then with deactivated cache and will report it.

I<Another known paragraph mode problem> occurs if you parse on a UNIX
system but your document (or parts of it) were written in DOS format. The
paragraph mode reads such a document I<completely>. Please replace the line
ending character sequences system appropriate. (If you are using C<dos2unix>
under Solaris please invoke it with option C<-ascii> to do this.)

More, Perls paragraph mode and PerlPoint treat whitespace lines differently.
Because of the way it works, paragraph mode does not recognize them as "empty"
while PerlPoint I<does> for reasons of usability (invisible characters should
not make a difference). This means that lines containing only whitespaces
separate PerlPoint paragraphs but not "Perl" paragraphs, making the cache
working wrong especially in examples. If paragraphs unintentionally disappear
in the resulting presentation, please check the "empty lines" before them.

Consistent cache data depend on the versions of the parser, of constant
declarations and of the module B<Storable> which is used internally. If the
parser detects a significant change in one of these versions, existing
caches are automatically rebuilt.

I<Final cache note:> cache files are not locked while they are used.
If you need this feature please let me know.

=item criticalSemanticErrors

If set to a true value, semantic errors will cause the parser to terminate
immediately. This defaults to false: errors are accumulated and finally
reported.

=item display

This parameter is optional. It controls the display of runtime messages
like informations or warnings. By default, all messages are displayed. You
can suppress these informations partially or completely by passing one or
more of the "DISPLAY_..." variables declared in B<PerlPoint::Constants>.
Constants should be combined by addition.

=item docstreams2skip

by default, all document streams are made part of the result, but by this
parameter one can I<exclude> certain streams (all remaining ones will be
streamed as usual).

The list should be supplied by an array reference.

It is suggested to take the values of this parameter from a user option,
which by convention should be named C<-skipstream>.

=item docstreaming

specifies the way the parser handles stream entry points. The value passed
might be either C<DSTREAM_DEFAULT>, C<DSTREAM_IGNORE> or C<DSTREAM_HEADLINES>.

C<DSTREAM_HEADLINES> instructs the parser to transform the entry points
into I<headlines>, one level below the current real headline level. This
is an easy to implement and convenient way of docstream handling seems to
make sense in most target formats.

C<DSTREAM_IGNORE> hides all streams except of the main stream. The effect
is similar to a call with I<docstreams2skip> set for all document streams
in a source.

C<DSTREAM_DEFAULT> treats the entry points as entry points and streams
them as such. This is the default if the parameter is omitted.

Please note that filters applied by I<docstream2skip> work regardless of
the I<docstreaming> configuration which only affects the way the parser
passes docstream data to a backend.

It is recommended to take the value of this parameter from a user option,
which by convention should be named C<-docstreaming>. (A converter can
define various more modes than provided by the parser and implement them
itself, of course. See C<pp2sdf> for a reference implementation.)


=item files

a reference to an array of files to be scanned.

Files are treated as PerlPoint sources except when their name has the
prefix C<IMPORT:>, as in C<IMPORT:podsource.pod>. With this prefix, the
parser tries to automatically tranform the source into PerlPoint,
using a standard import filter for the format indicated by the file
extension (C<pod> in our example). The filter must be installed as
C<PerlPoint::Import::E<lt>uppercased format nameE<gt>>, e.g.
C<PerlPoint::Import::POD>.

=item filter

a regular expression describing the target language. This setting, if used,
prevents all embedded or included source code of other languages than the set
one from inclusion into the generated stream. This accelerates both parsing
and backend handling. The pattern is evaluated case insensitively.

 Example: pass "html|perl" to allow HTML and Perl.

To illustrate this, imagine a translator to PostScript. If it reads a Perl
Point file which includes native HTML, this translator cannot handle such code.
The backend would have to skip the HTML statements. With a "PostScript" filter,
the HTML code will not appear in the stream.

This enables PerlPoint texts prepared for various target languages. If an
author really needs plain target language code to be embedded into PerlPoint,
he could provide versions for various languages. Translators using a filter
will then receive exactly the code of their target language, if provided.

Please note that you cannot filter out PerlPoint code or example files.

By default, no filter is set.


=item headlineLinks

this optional flag causes the parser to register all headline
titles as anchors automatically. (Headlines are stored without
possibly included tags which are stripped off.)

Registering anchors does \I<not> mean there are anchors included
to the stream, it just means that they are known to exist at
parsing time because they are added to an internal C<PerlPoint::Anchor>
object which is passed to all tag hooks and can be evaluated there.
See \C<PerlPoint::Tags> and C<PerlPoint::Anchors> for details.

It is recommended to make use of this feature if your converter
automatically makes headlines an anchor named like the headline
(this feature was introduced by Lorenz Domkes C<pp2html> initially).
(Nevertheless, usefulness may depend on dealing with the parsers
anchor collection in tag hooks. See the documentations of used
tag modules for details.)

If your converter does not support automatic headline anchors

lib/PerlPoint/Parser.pm  view on Meta::CPAN

parsed files.

B<Example:>

  $parser->run(
               stream => \@streamData,
               files  => \@ARGV,
               filter => 'HTML',
               cache  => CACHE_ON,
               trace  => TRACE_PARAGRAPHS,
              );

=cut
sub run
 {
  # get parameters
  my ($me, @pars)=@_;

  # build parameter hash
  confess "[BUG] The number of parameters should be even.\n" if @pars%2;
  my %pars=@pars;

  # and check parameters
  confess "[BUG] Missing object parameter.\n" unless $me;
  confess "[BUG] Object parameter is no ", __PACKAGE__, " object.\n" unless ref $me and ref $me eq __PACKAGE__;
  confess "[BUG] Missing stream array reference parameter.\n" unless $pars{stream};
  confess "[BUG] Stream array reference parameter is no array reference.\n" unless ref $pars{stream} and ref $pars{stream} eq 'ARRAY';
  confess "[BUG] Missing file list reference parameter.\n" unless $pars{files};
  confess "[BUG] File list reference parameter is no array reference.\n" unless ref $pars{files} and ref $pars{files} eq 'ARRAY';
  confess "[BUG] You should pass at least one file to parse.\n" unless @{$pars{files}};
  confess "[BUG] Active base data reference is no hash reference.\n" if exists $pars{activeBaseData} and ref $pars{activeBaseData} ne 'HASH';
  confess "[BUG] Active data initializer is no code reference.\n" if exists $pars{activeDataInit} and ref $pars{activeDataInit} ne 'CODE';
  confess "[BUG] Document stream skip list is no array reference.\n" if exists $pars{docstreams2skip} and ref $pars{docstreams2skip} ne 'ARRAY';
  if (exists $pars{filter})
    {
     eval "'lang'=~/$pars{filter}/";
     confess qq([BUG] Invalid filter expression "$pars{filter}": $@.\n) if $@;
    }

  # variables
  my ($rc, %docHints)=(1);

  # init internal data
  (
   $resultStreamRef,          #  1
   $safeObject,               #  2
   $flags{trace},             #  3
   $flags{display},           #  4
   $flags{filter},            #  5
   $flags{linehints},         #  6
   $flags{var2stream},        #  7
   $flags{cache},             #  8
   $flags{cached},            #  9
   $flags{vis},               # 10
   $flags{activeBaseData},    # 11
   $flags{activeDataInit},    # 12
   $flags{nestedTables},      # 13
   $flags{headlineLinks},     # 14
   $flags{skipcomments},      # 15
   $flags{docstreams2skip},   # 16
   $flags{docstreaming},      # 17
   $flags{criticalSemantics}, # 18
   $macroChecksum,            # 19
   $varChecksum,              # 20
   $anchors,                  # 21
  )=(
     $pars{stream},                                                                         #  1
     (                                                                                      #  2
          exists $pars{safe}
      and defined $pars{safe}
     ) ? ref($pars{safe}) eq 'Safe' ? $pars{safe}
                                    : 1
       : 0,
     exists $pars{trace} ? $pars{trace} : TRACE_NOTHING,                                    #  3
     exists $pars{display} ? $pars{display} : DISPLAY_ALL,                                  #  4
     exists $pars{filter} ? $pars{filter} : '',                                             #  5
     (exists $pars{linehints} and $pars{linehints}),                                        #  6
     (exists $pars{var2stream} and $pars{var2stream}),                                      #  7
     exists $pars{cache} ? $pars{cache} : CACHE_OFF,                                        #  8
     0,                                                                                     #  9
     exists $pars{vispro} ? $pars{vispro} : 0,                                              # 10
     exists $pars{activeBaseData} ? $pars{activeBaseData} : 0,                              # 11
     exists $pars{activeDataInit} ? $pars{activeDataInit} : 0,                              # 12
     exists $pars{nestedTables} ? $pars{nestedTables} : 0,                                  # 13
     exists $pars{headlineLinks} ? $pars{headlineLinks} : 0,                                # 14
     (exists $pars{skipcomments} and $pars{skipcomments}),                                  # 15
     exists $pars{docstreams2skip} ? {map {($_ => undef)} @{$pars{docstreams2skip}}} : 0,   # 16
     exists $pars{docstreaming} ? $pars{docstreaming} : DSTREAM_DEFAULT,                    # 17
     exists $pars{criticalSemanticErrors} ? $pars{criticalSemanticErrors} : 0,              # 18
     0,                                                                                     # 19
     0,                                                                                     # 20
     PerlPoint::Anchors->new,                                                               # 21
    );

  # prepare stream data structure and appropriate handlers
  unless (@$resultStreamRef and $resultStreamRef->[STREAM_IDENT] eq '__PerlPoint_stream__')
    {
     # empty stream
     @$resultStreamRef=();
     # initiate
     @{$resultStreamRef}[
                         STREAM_IDENT,
                         STREAM_TOKENS,
                         STREAM_HEADLINES,
                        ]=(
                           '__PerlPoint_stream__',     # stream identifier;
                           [],                         # base stream;
                           [],                         # headline stream;
                          );
    }

  # declare helper subroutines to be used in active contents
  if ($safeObject)
    {
     my $code=<<'EOC';

  unless (defined *main::flagSet{CODE})
   {
    # check if at least one of a set of flags is set
    # - define functions anonymously to avoid redefinition in case the condition is not matched
    *main::flagSet=sub
                    {
                     # declare and init variable
                     my $rc=0;

                     # check flags
                     foreach (@_)
                       {$rc=1, last if exists $PerlPoint->{userSettings}{$_};}

                     # supply result
                     $rc;
                    };

    # provide the value of a PerlPoint variable
    # - define function anonymously to avoid redefinition in case the condition is not matched
    *main::varValue=sub {${join('::', 'main', $_[0])};};
   }

# complete compartment code
EOC

     ref($safeObject) ? $safeObject->reval($code) : eval("{package main; no strict; $code}");
     die "[BUG] Bug in function definition, please inform developer: $@" if $@;
    }

  # predeclare variables
  _predeclareVariables({_PARSER_VERSION=>$PerlPoint::Parser::VERSION, _STARTDIR=>cwd()});

lib/PerlPoint/Parser.pm  view on Meta::CPAN

  $helperBackend->bind($resultStreamRef);
  my $toc=$helperBackend->toc;

  # store headlines as anchors, if necessary
  if (@$toc and $flags{headlineLinks})
    {
     # scopies
     my ($headlineNr, @headlinePath)=(0);

     foreach (@$toc)
       {
        # update headline counter
        $headlineNr++;

        # get data
        my ($level, $title)=@$_;

        # skip empty headlines
        next unless $title;

        # update headline path and numbers
        $headlinePath[$level]=$title;

        # store both plain and composite headlines in the anchor object
        $anchors->add($title, $title, $headlineNr);
        $anchors->add(join('|', map {defined($_) ? $_ : ''} @headlinePath[$_..$level]), $title, $headlineNr) for (1..$level-1);
       }
    }

  # add complete headline titles to streamed headline tokens,
  # move abbreviation, docstream and variable hints into data section
  if (@$toc)
   {
    # scopy
    my (@headlinePath, @shortcutPath, @levelPath, @pagenumPath);

    for (my $index=0; $index<=$#{$toc}; ++$index)
     {
      # build a more readable shortcut
      my $ref=$resultStreamRef->[STREAM_TOKENS][$resultStreamRef->[STREAM_HEADLINES][$index]];

      # get toc data
      my ($level, $title)=@{$toc->[$index]};

      # adapt arrays to get rid of previous data - important in case someone skips several levels
      # (jumping from level 5 to 100 etc.)
      $#headlinePath=$#shortcutPath=$#levelPath=$#pagenumPath=$level;

      # update headline pathes and numbers
      $headlinePath[$level]=$title;
      $shortcutPath[$level]=$ref->[0]{shortcut} ? $ref->[0]{shortcut} : $title;
      $levelPath[$level]++;
      $pagenumPath[$level]=$index+1;  # real page number, no index

      my $docstreams=delete($ref->[0]{docstreams});
      my $variables=delete($ref->[0]{vars});
      push (
            @$ref,
            $toc->[$index][1],
            delete($ref->[0]{shortcut}),
            $flags{docstreaming}==DSTREAM_DEFAULT ? [sort keys %$docstreams] : {},

            # store headline path data in the streamed token
            [
             dclone([@headlinePath[1..$level]]),
             dclone([@shortcutPath[1..$level]]),
             dclone([@levelPath[1..$level]]),
             dclone([@pagenumPath[1..$level]]),
             $variables,
            ],
           );
     }
   }

  # finish tags, if necessary
  if (@$pendingTags==3)
   {
    # get number of tokens
    my $lastIndex=$#{$resultStreamRef->[STREAM_TOKENS]};

    # handle all marked sections
    foreach my $section (@{$pendingTags->[2]})
      {
       # scan the stream till all pending tags were handled,
       # begin as near as possible
       for (my $position=$section->[0]; $position<=$lastIndex; $position++)
        {
         # get token
         my $token=$resultStreamRef->[STREAM_TOKENS][$position];

         # skip everything except tag beginners of tags with finish hooks
         next unless     ref($token)
                     and $token->[STREAM_DIR_TYPE]==DIRECTIVE_TAG
                     and $token->[STREAM_DIR_STATE]==DIRECTIVE_START
                     and exists $tagsRef->{$token->[STREAM_DIR_DATA]}{finish};

         # make an option hash
         my $options=dclone($token->[STREAM_DIR_DATA+1]);

         # call hook function (use eval() to guard yourself)
         my $rc;
         eval {$rc=&{$tagsRef->{$token->[STREAM_DIR_DATA]}{finish}}($options, $anchors, join('-', @headlineIds))};

         # check result
         unless ($@)
          {
           {
            # Error? (Treat syntactic errors as semantic ones at this point to give PARSING_FAILED a meaning.)
            ++$_semerr, last if $rc==PARSING_ERROR or $rc==PARSING_FAILED;

            # update options (might be modified, and checking for a difference
            # might take more time then just copying the replied values)
            $token->[STREAM_DIR_DATA+1]=$options;

            # all right? (several values just mean "ok" at this point)
            last if $rc==PARSING_OK or $rc==PARSING_COMPLETED;

            # backend hints to store?
            $token->[STREAM_DIR_HINTS]{ignore}=1, last if $rc==PARSING_IGNORE;
            $token->[STREAM_DIR_HINTS]{hide}=1,   last if $rc==PARSING_ERASE;



( run in 0.481 second using v1.01-cache-2.11-cpan-140bd7fdf52 )