| #=== HTML::Toc ================================================================ |
| # function: HTML Table of Contents |
| |
| |
| package HTML::Toc; |
| |
| |
| use strict; |
| |
| |
| BEGIN { |
| use vars qw($VERSION); |
| |
| $VERSION = '0.91'; |
| } |
| |
| |
| use constant FILE_FILTER => '.*'; |
| use constant GROUP_ID_H => 'h'; |
| use constant LEVEL_1 => 1; |
| use constant NUMBERING_STYLE_DECIMAL => 'decimal'; |
| |
| # Templates |
| |
| # Anchor templates |
| use constant TEMPLATE_ANCHOR_NAME => '$groupId."-".$node'; |
| use constant TEMPLATE_ANCHOR_HREF_BEGIN => |
| '"<a href=#$anchorName>"'; |
| use constant TEMPLATE_ANCHOR_HREF_BEGIN_FILE => |
| '"<a href=$file#$anchorName>"'; |
| use constant TEMPLATE_ANCHOR_HREF_END => '"</a>"'; |
| use constant TEMPLATE_ANCHOR_NAME_BEGIN => |
| '"<a name=$anchorName>"'; |
| use constant TEMPLATE_ANCHOR_NAME_END => '"</a>"'; |
| use constant TOKEN_UPDATE_BEGIN_OF_ANCHOR_NAME_BEGIN => |
| '<!-- #BeginTocAnchorNameBegin -->'; |
| use constant TOKEN_UPDATE_END_OF_ANCHOR_NAME_BEGIN => |
| '<!-- #EndTocAnchorNameBegin -->'; |
| use constant TOKEN_UPDATE_BEGIN_OF_ANCHOR_NAME_END => |
| '<!-- #BeginTocAnchorNameEnd -->'; |
| use constant TOKEN_UPDATE_END_OF_ANCHOR_NAME_END => |
| '<!-- #EndTocAnchorNameEnd -->'; |
| use constant TOKEN_UPDATE_BEGIN_NUMBER => |
| '<!-- #BeginTocNumber -->'; |
| use constant TOKEN_UPDATE_END_NUMBER => |
| '<!-- #EndTocNumber -->'; |
| use constant TOKEN_UPDATE_BEGIN_TOC => |
| '<!-- #BeginToc -->'; |
| use constant TOKEN_UPDATE_END_TOC => |
| '<!-- #EndToc -->'; |
| |
| use constant TEMPLATE_TOKEN_NUMBER => '"$node "'; |
| |
| # Level templates |
| use constant TEMPLATE_LEVEL => '"<li>$text\n"'; |
| use constant TEMPLATE_LEVEL_BEGIN => '"<ul>\n"'; |
| use constant TEMPLATE_LEVEL_END => '"</ul>\n"'; |
| |
| |
| END {} |
| |
| |
| #--- HTML::Toc::new() --------------------------------------------------------- |
| # function: Constructor |
| |
| sub new { |
| # Get arguments |
| my ($aType) = @_; |
| # Local variables |
| my $self; |
| |
| $self = bless({}, $aType); |
| # Default to empty 'options' array |
| $self->{options} = {}; |
| # Empty toc |
| $self->{_toc} = ""; |
| # Hash reference to array for each groupId, each array element |
| # referring to the group of the level indicated by the array index. |
| # For example, with the default 'tokenGroups', '_levelGroups' would |
| # look like: |
| # |
| # {'h'} => [\$group1, \$group2, \$group3, \$group4, \$group5, \$group6]; |
| # |
| $self->{_levelGroups} = undef; |
| # Set default options |
| $self->_setDefaults(); |
| return $self; |
| } # new() |
| |
| |
| #--- HTML::Toc::_compareLevels() ---------------------------------------------- |
| # function: Compare levels. |
| # args: - $aLevel: pointer to level |
| # - $aGroupLevel |
| # - $aPreviousLevel |
| # - $aPreviousGroupLevel |
| # returns: 0 if new level equals previous level, 1 if new level exceeds |
| # previous level, -1 if new level is smaller then previous level. |
| |
| sub _compareLevels { |
| # Get arguments |
| my ( |
| $self, $aLevel, $aPreviousLevel, $aGroupLevel, $aPreviousGroupLevel |
| ) = @_; |
| # Local variables |
| my ($result); |
| # Levels equals? |
| if ( |
| ($aLevel == $aPreviousLevel) && |
| ($aGroupLevel == $aPreviousGroupLevel) |
| ) { |
| # Yes, levels are equals; |
| # Indicate so |
| $result = 0; |
| } |
| else { |
| # No, levels differ; |
| # Bias to new level being smaller than previous level; |
| $result = -1; |
| # Must groups not be nested and do group levels differ? |
| if ( |
| ($self->{options}{'doNestGroup'} == 0) && |
| ($aGroupLevel != $aPreviousGroupLevel) |
| ) { |
| # Yes, groups must be kept apart and the group levels differ; |
| # Level is greater than previous level? |
| if ( |
| ($aLevel > $aPreviousLevel) |
| ) { |
| # Yes, level is greater than previous level; |
| # Indicate so |
| $result = 1; |
| } |
| } |
| else { |
| # No, group must be nested; |
| # Level is greater than previous level? |
| if ( |
| ($aLevel > $aPreviousLevel) || |
| ($aGroupLevel > $aPreviousGroupLevel) |
| ) { |
| # Yes, level is greater than previous level; |
| # Indicate so |
| $result = 1; |
| } |
| } |
| } |
| # Return value |
| return $result; |
| } # _compareLevels() |
| |
| |
| #--- HTML::TocGenerator::_formatLevelIndent() --------------------------------- |
| # function: Format indent. |
| # args: - $aText: text to indent |
| # - $aLevel: Level. |
| # - $aGroupLevel: Group level. |
| # - $aAdd |
| # - $aGlobalLevel |
| |
| sub _formatLevelIndent { |
| # Get arguments |
| my ($self, $aText, $aAdd, $aGlobalLevel) = @_; |
| # Local variables |
| my ($levelIndent, $indent, $nrOfIndents); |
| # Alias indentation option |
| $levelIndent = $self->{options}{'levelIndent'}; #=~ s/[0-9]+/&/; |
| # Calculate number of indents |
| $nrOfIndents = ($aGlobalLevel + $aAdd) * $levelIndent; |
| # Assemble indents |
| $indent = pack("A$nrOfIndents"); |
| # Return value |
| return $indent . $aText; |
| } # _formatLevelIndent() |
| |
| |
| #--- HTML::Toc::_formatToc() -------------------------------------------------- |
| # function: Format ToC. |
| # args: - aPreviousLevel |
| # - aPreviousGroupLevel |
| # - aToc: ToC to format. |
| # - aHeaderLines |
| # note: Recursive function this is. |
| |
| sub _formatToc { |
| # Get arguments |
| my ( |
| $self, $aPreviousLevel, $aPreviousGroupLevel, $aToc, $aHeaderLines, |
| $aGlobalLevel |
| ) = @_; |
| # Local variables |
| my ($level, $groupLevel, $line, $groupId, $text, $compareStatus); |
| my ($anchorName, $globalLevel, $node, $sequenceNr); |
| |
| LOOP: { |
| # Lines need processing? |
| while (scalar(@$aHeaderLines) > 0) { |
| # Yes, lines need processing; |
| # Get line |
| $line = shift @$aHeaderLines; |
| |
| # Determine levels |
| ($level, $groupLevel, $groupId, $node, $sequenceNr, |
| $anchorName, $text) = split( |
| / /, $line, 7 |
| ); |
| # Must level and group be processed? |
| if ( |
| ($level =~ m/$self->{options}{'levelToToc'}/) && |
| ($groupId =~ m/$self->{options}{'groupToToc'}/) |
| ) { |
| # Yes, level must be processed; |
| # Compare levels |
| $compareStatus = $self->_compareLevels( |
| $level, $aPreviousLevel, $groupLevel, $aPreviousGroupLevel |
| ); |
| |
| COMPARE_LEVELS: { |
| |
| # Equals? |
| if ($compareStatus == 0) { |
| # Yes, levels are equal; |
| # Format level |
| $$aToc .= $self->_formatLevelIndent( |
| ref($self->{_templateLevel}) eq "CODE" ? |
| &{$self->{_templateLevel}}( |
| $level, $groupId, $node, $sequenceNr, $text |
| ) : |
| eval($self->{_templateLevel}), |
| 0, $aGlobalLevel |
| ); |
| } |
| |
| # Greater? |
| if ($compareStatus > 0) { |
| # Yes, new level is greater than previous level; |
| # Must level be single-stepped? |
| if ( |
| $self->{options}{'doSingleStepLevel'} && |
| ($aPreviousLevel) && |
| ($level > $aPreviousLevel) |
| ) { |
| # Yes, level must be single-stepped; |
| # Make sure, new level is increased one step only |
| $level = $aPreviousLevel + 1; |
| } |
| # Increase global level |
| $aGlobalLevel++; |
| # Format begin of level |
| $$aToc .= $self->_formatLevelIndent( |
| eval($self->{_templateLevelBegin}), -1, $aGlobalLevel |
| ); |
| # Process line again |
| unshift @$aHeaderLines, $line; |
| # Assemble TOC (recursive) for next level |
| $self->_formatToc( |
| $level, $groupLevel, $aToc, $aHeaderLines, $aGlobalLevel |
| ); |
| # Format end of level |
| $$aToc .= $self->_formatLevelIndent( |
| eval($self->{_templateLevelEnd}), -1, $aGlobalLevel |
| ); |
| # Decrease global level |
| $aGlobalLevel--; |
| # Exit loop |
| last COMPARE_LEVELS; |
| } |
| |
| # Smaller? |
| if ($compareStatus < 0) { |
| # Yes, new level is smaller than previous level; |
| # Process line again |
| unshift @$aHeaderLines, $line; |
| # End loop |
| last LOOP; |
| } |
| } |
| } |
| } |
| } |
| } # _formatToc() |
| |
| |
| #--- HTML::Toc::_parseTokenGroups() ------------------------------------------- |
| # function: Parse token groups |
| |
| sub _parseTokenGroups { |
| # Get arguments |
| my ($self) = @_; |
| # Local variables |
| my ($group, $levelGroups, $numberingStyle); |
| |
| # Clear any previous 'levelGroups' |
| $self->{_levelGroups} = undef; |
| # Determine default 'numberingStyle' |
| $numberingStyle = defined($self->{options}{'numberingStyle'}) ? |
| $self->{options}{'numberingStyle'} : NUMBERING_STYLE_DECIMAL; |
| |
| # Loop through groups |
| foreach $group (@{$self->{options}{'tokenToToc'}}) { |
| # 'groupId' is specified? |
| if (! defined($group->{'groupId'})) { |
| # No, 'groupId' isn't specified; |
| # Set default groupId |
| $group->{'groupId'} = GROUP_ID_H; |
| } |
| # 'level' is specified? |
| if (! defined($group->{'level'})) { |
| # No, 'level' isn't specified; |
| # Set default level |
| $group->{'level'} = LEVEL_1; |
| } |
| # 'numberingStyle' is specified? |
| if (! defined($group->{'numberingStyle'})) { |
| # No, 'numberingStyle' isn't specified; |
| # Set default numberingStyle |
| $group->{'numberingStyle'} = $numberingStyle; |
| } |
| # Add group to '_levelGroups' variabele |
| $self->{_levelGroups}{$group->{'groupId'}}[$group->{'level'} - 1] = |
| $group; |
| } |
| } # _parseTokenGroups() |
| |
| |
| #--- HTML::Toc::_setDefaults() ------------------------------------------------ |
| # function: Set default options. |
| |
| sub _setDefaults { |
| # Get arguments |
| my ($self) = @_; |
| # Set default options |
| $self->setOptions( |
| { |
| 'attributeToExcludeToken' => '-', |
| 'attributeToTocToken' => '@', |
| 'insertionPoint' => 'after <body>', |
| 'levelToToc' => '.*', |
| 'groupToToc' => '.*', |
| 'doNumberToken' => 0, |
| 'doLinkToFile' => 0, |
| 'doLinkToToken' => 1, |
| 'doLinkToId' => 0, |
| 'doSingleStepLevel' => 1, |
| 'linkUri' => '', |
| 'levelIndent' => 3, |
| 'doNestGroup' => 0, |
| 'doUseExistingAnchors' => 1, |
| 'doUseExistingIds' => 1, |
| 'tokenToToc' => [ |
| { |
| 'level' => 1, |
| 'tokenBegin' => '<h1>' |
| }, { |
| 'level' => 2, |
| 'tokenBegin' => '<h2>' |
| }, { |
| 'level' => 3, |
| 'tokenBegin' => '<h3>' |
| }, { |
| 'level' => 4, |
| 'tokenBegin' => '<h4>' |
| }, { |
| 'level' => 5, |
| 'tokenBegin' => '<h5>' |
| }, { |
| 'level' => 6, |
| 'tokenBegin' => '<h6>' |
| } |
| ], |
| 'header' => |
| "\n<!-- Table of Contents generated by Perl - HTML::Toc -->\n", |
| 'footer' => |
| "\n<!-- End of generated Table of Contents -->\n", |
| } |
| ); |
| } # _setDefaults() |
| |
| |
| #--- HTML::Toc::clear() ------------------------------------------------------- |
| # function: Clear ToC. |
| |
| sub clear { |
| # Get arguments |
| my ($self) = @_; |
| # Clear ToC |
| $self->{_toc} = ""; |
| $self->{toc} = ""; |
| $self->{groupIdLevels} = undef; |
| $self->{levels} = undef; |
| } # clear() |
| |
| |
| #--- HTML::Toc::format() ------------------------------------------------------ |
| # function: Format ToC. |
| # returns: Formatted ToC. |
| |
| sub format { |
| # Get arguments |
| my ($self) = @_; |
| # Local variables; |
| my $toc = ""; |
| my @tocLines = split(/\r\n|\n/, $self->{_toc}); |
| # Format table of contents |
| $self->_formatToc("0", "0", \$toc, \@tocLines, 0); |
| # Remove last newline |
| $toc =~ s/\n$//m; |
| # Add header & footer |
| $toc = $self->{options}{'header'} . $toc . $self->{options}{'footer'}; |
| # Return value |
| return $toc; |
| } # format() |
| |
| |
| #--- HTML::Toc::parseOptions() ------------------------------------------------ |
| # function: Parse options. |
| |
| sub parseOptions { |
| # Get arguments |
| my ($self) = @_; |
| # Alias options |
| my $options = $self->{options}; |
| |
| # Parse token groups |
| $self->_parseTokenGroups(); |
| |
| # Link ToC to tokens? |
| if ($self->{options}{'doLinkToToken'}) { |
| # Yes, link ToC to tokens; |
| # Determine anchor href template begin |
| $self->{_templateAnchorHrefBegin} = |
| defined($options->{'templateAnchorHrefBegin'}) ? |
| $options->{'templateAnchorHrefBegin'} : |
| $options->{'doLinkToFile'} ? |
| TEMPLATE_ANCHOR_HREF_BEGIN_FILE : TEMPLATE_ANCHOR_HREF_BEGIN; |
| |
| # Determine anchor href template end |
| $self->{_templateAnchorHrefEnd} = |
| defined($options->{'templateAnchorHrefEnd'}) ? |
| $options->{'templateAnchorHrefEnd'} : |
| TEMPLATE_ANCHOR_HREF_END; |
| |
| # Determine anchor name template |
| $self->{_templateAnchorName} = |
| defined($options->{'templateAnchorName'}) ? |
| $options->{'templateAnchorName'} : |
| TEMPLATE_ANCHOR_NAME; |
| |
| # Determine anchor name template begin |
| $self->{_templateAnchorNameBegin} = |
| defined($options->{'templateAnchorNameBegin'}) ? |
| $options->{'templateAnchorNameBegin'} : |
| TEMPLATE_ANCHOR_NAME_BEGIN; |
| |
| # Determine anchor name template end |
| $self->{_templateAnchorNameEnd} = |
| defined($options->{'templateAnchorNameEnd'}) ? |
| $options->{'templateAnchorNameEnd'} : |
| TEMPLATE_ANCHOR_NAME_END; |
| } |
| |
| # Determine token number template |
| $self->{_templateTokenNumber} = |
| defined($options->{'templateTokenNumber'}) ? |
| $options->{'templateTokenNumber'} : |
| TEMPLATE_TOKEN_NUMBER; |
| |
| # Determine level template |
| $self->{_templateLevel} = |
| defined($options->{'templateLevel'}) ? |
| $options->{'templateLevel'} : |
| TEMPLATE_LEVEL; |
| |
| # Determine level begin template |
| $self->{_templateLevelBegin} = |
| defined($options->{'templateLevelBegin'}) ? |
| $options->{'templateLevelBegin'} : |
| TEMPLATE_LEVEL_BEGIN; |
| |
| # Determine level end template |
| $self->{_templateLevelEnd} = |
| defined($options->{'templateLevelEnd'}) ? |
| $options->{'templateLevelEnd'} : |
| TEMPLATE_LEVEL_END; |
| |
| # Determine 'anchor name begin' begin update token |
| $self->{_tokenUpdateBeginOfAnchorNameBegin} = |
| defined($options->{'tokenUpdateBeginOfAnchorNameBegin'}) ? |
| $options->{'tokenUpdateBeginOfAnchorNameBegin'} : |
| TOKEN_UPDATE_BEGIN_OF_ANCHOR_NAME_BEGIN; |
| |
| # Determine 'anchor name begin' end update token |
| $self->{_tokenUpdateEndOfAnchorNameBegin} = |
| defined($options->{'tokenUpdateEndOfAnchorNameBegin'}) ? |
| $options->{'tokenUpdateEndOfAnchorNameBegin'} : |
| TOKEN_UPDATE_END_OF_ANCHOR_NAME_BEGIN; |
| |
| # Determine 'anchor name end' begin update token |
| $self->{_tokenUpdateBeginOfAnchorNameEnd} = |
| defined($options->{'tokenUpdateBeginOfAnchorNameEnd'}) ? |
| $options->{'tokenUpdateBeginOfAnchorNameEnd'} : |
| TOKEN_UPDATE_BEGIN_OF_ANCHOR_NAME_END; |
| |
| # Determine 'anchor name end' end update token |
| $self->{_tokenUpdateEndOfAnchorNameEnd} = |
| defined($options->{'tokenUpdateEndOfAnchorNameEnd'}) ? |
| $options->{'tokenUpdateEndOfAnchorNameEnd'} : |
| TOKEN_UPDATE_END_OF_ANCHOR_NAME_END; |
| |
| # Determine number begin update token |
| $self->{_tokenUpdateBeginNumber} = |
| defined($options->{'tokenUpdateBeginNumber'}) ? |
| $options->{'tokenUpdateBeginNumber'} : |
| TOKEN_UPDATE_BEGIN_NUMBER; |
| |
| # Determine number end update token |
| $self->{_tokenUpdateEndNumber} = |
| defined($options->{'tokenUpdateEndNumber'}) ? |
| $options->{'tokenUpdateEndNumber'} : |
| TOKEN_UPDATE_END_NUMBER; |
| |
| # Determine toc begin update token |
| $self->{_tokenUpdateBeginToc} = |
| defined($options->{'tokenUpdateBeginToc'}) ? |
| $options->{'tokenUpdateBeginToc'} : |
| TOKEN_UPDATE_BEGIN_TOC; |
| |
| # Determine toc end update token |
| $self->{_tokenUpdateEndToc} = |
| defined($options->{'tokenUpdateEndToc'}) ? |
| $options->{'tokenUpdateEndToc'} : |
| TOKEN_UPDATE_END_TOC; |
| |
| } # parseOptions() |
| |
| |
| #--- HTML::Toc::setOptions() -------------------------------------------------- |
| # function: Set options. |
| # args: - aOptions: Reference to hash containing options. |
| |
| sub setOptions { |
| # Get arguments |
| my ($self, $aOptions) = @_; |
| # Add options |
| %{$self->{options}} = (%{$self->{options}}, %$aOptions); |
| } # setOptions() |
| |
| |
| 1; |