File Coverage

blib/lib/HTML/Toc.pm
Criterion Covered Total %
statement 190 192 98.9
branch 75 78 96.1
condition 24 24 100.0
subroutine 37 37 100.0
pod 2 5 40.0
total 328 336 97.6


line stmt bran cond sub pod time code
1             #=== HTML::Toc ================================================================
2             # function: HTML Table of Contents
3              
4              
5             package HTML::Toc;
6              
7              
8 12     12   496243 use strict;
  12         29  
  12         624  
9              
10              
11             BEGIN {
12 12     12   68 use vars qw($VERSION);
  12         24  
  12         685  
13              
14 12     12   269 $VERSION = '1.12';
15             }
16              
17              
18 12     12   73 use constant FILE_FILTER => '.*';
  12         25  
  12         834  
19 12     12   64 use constant GROUP_ID_H => 'h';
  12         27  
  12         640  
20 12     12   68 use constant LEVEL_1 => 1;
  12         18  
  12         558  
21 12     12   65 use constant NUMBERING_STYLE_DECIMAL => 'decimal';
  12         21  
  12         663  
22              
23             # Templates
24              
25             # Anchor templates
26 12     12   57 use constant TEMPLATE_ANCHOR_NAME => '$groupId."-".$node';
  12         29  
  12         534  
27 12         608 use constant TEMPLATE_ANCHOR_HREF_BEGIN =>
28 12     12   59 '""';
  12         20  
29 12         635 use constant TEMPLATE_ANCHOR_HREF_BEGIN_FILE =>
30 12     12   124 '""';
  12         26  
31 12     12   62 use constant TEMPLATE_ANCHOR_HREF_END => '""';
  12         70  
  12         526  
32 12         520 use constant TEMPLATE_ANCHOR_NAME_BEGIN =>
33 12     12   55 '""';
  12         19  
34 12     12   56 use constant TEMPLATE_ANCHOR_NAME_END => '""';
  12         18  
  12         511  
35 12         581 use constant TOKEN_UPDATE_BEGIN_OF_ANCHOR_NAME_BEGIN =>
36 12     12   54 '';
  12         19  
37 12         518 use constant TOKEN_UPDATE_END_OF_ANCHOR_NAME_BEGIN =>
38 12     12   98 '';
  12         22  
39 12         561 use constant TOKEN_UPDATE_BEGIN_OF_ANCHOR_NAME_END =>
40 12     12   54 '';
  12         24  
41 12         670 use constant TOKEN_UPDATE_END_OF_ANCHOR_NAME_END =>
42 12     12   53 '';
  12         20  
43 12         496 use constant TOKEN_UPDATE_BEGIN_NUMBER =>
44 12     12   55 '';
  12         18  
45 12         488 use constant TOKEN_UPDATE_END_NUMBER =>
46 12     12   57 '';
  12         20  
47 12         606 use constant TOKEN_UPDATE_BEGIN_TOC =>
48 12     12   56 '';
  12         14  
49 12         475 use constant TOKEN_UPDATE_END_TOC =>
50 12     12   58 '';
  12         22  
51              
52 12     12   49 use constant TEMPLATE_TOKEN_NUMBER => '"$node  "';
  12         26  
  12         677  
53              
54             # Level templates
55 12     12   56 use constant TEMPLATE_LEVEL => '"
  • $text"';
  •   12         18  
      12         482  
    56 12     12   67 use constant TEMPLATE_LEVEL_CLOSE => '"\n"';
      12         26  
      12         605  
    57 12     12   69 use constant TEMPLATE_LEVEL_BEGIN => '"
      \n"';
      12         35  
      12         581  
    58 12     12   58 use constant TEMPLATE_LEVEL_END => '"\n"';
      12         21  
      12         25291  
    59              
    60              
    61 12     12   52 END {}
    62              
    63              
    64             #--- HTML::Toc::new() ---------------------------------------------------------
    65             # function: Constructor
    66              
    67             sub new {
    68             # Get arguments
    69 48     48 0 29307 my ($aType) = @_;
    70             # Local variables
    71 48         308 my $self;
    72              
    73 48         140 $self = bless({}, $aType);
    74             # Default to empty 'options' array
    75 48         597 $self->{options} = {};
    76             # Empty toc
    77 48         115 $self->{_toc} = "";
    78             # Hash reference to array for each groupId, each array element
    79             # referring to the group of the level indicated by the array index.
    80             # For example, with the default 'tokenGroups', '_levelGroups' would
    81             # look like:
    82             #
    83             # {'h'} => [\$group1, \$group2, \$group3, \$group4, \$group5, \$group6];
    84             #
    85 48         96 $self->{_levelGroups} = undef;
    86             # Set default options
    87 48         167 $self->_setDefaults();
    88 48         437 return $self;
    89             } # new()
    90              
    91              
    92             #--- HTML::Toc::_compareLevels() ----------------------------------------------
    93             # function: Compare levels.
    94             # args: - $aLevel: pointer to level
    95             # - $aGroupLevel
    96             # - $aPreviousLevel
    97             # - $aPreviousGroupLevel
    98             # returns: 0 if new level equals previous level, 1 if new level exceeds
    99             # previous level, -1 if new level is smaller then previous level.
    100              
    101             sub _compareLevels {
    102             # Get arguments
    103             my (
    104 412     412   724 $self, $aLevel, $aPreviousLevel, $aGroupLevel, $aPreviousGroupLevel
    105             ) = @_;
    106             # Local variables
    107 412         419 my ($result);
    108             # Levels equals?
    109 412 100 100     1825 if (
    110             ($aLevel == $aPreviousLevel) &&
    111             ($aGroupLevel == $aPreviousGroupLevel)
    112             ) {
    113             # Yes, levels are equals;
    114             # Indicate so
    115 230         444 $result = 0;
    116             }
    117             else {
    118             # No, levels differ;
    119             # Bias to new level being smaller than previous level;
    120 182         229 $result = -1;
    121             # Must groups not be nested and do group levels differ?
    122 182 100 100     826 if (
    123             ($self->{options}{'doNestGroup'} == 0) &&
    124             ($aGroupLevel != $aPreviousGroupLevel)
    125             ) {
    126             # Yes, groups must be kept apart and the group levels differ;
    127             # Level is greater than previous level?
    128 97 100       220 if (
    129             ($aLevel > $aPreviousLevel)
    130             ) {
    131             # Yes, level is greater than previous level;
    132             # Indicate so
    133 91         141 $result = 1;
    134             }
    135             }
    136             else {
    137             # No, group must be nested;
    138             # Level is greater than previous level?
    139 85 100 100     344 if (
    140             ($aLevel > $aPreviousLevel) ||
    141             ($aGroupLevel > $aPreviousGroupLevel)
    142             ) {
    143             # Yes, level is greater than previous level;
    144             # Indicate so
    145 54         78 $result = 1;
    146             }
    147             }
    148             }
    149             # Return value
    150 412         764 return $result;
    151             } # _compareLevels()
    152              
    153              
    154             #--- HTML::TocGenerator::_formatLevelIndent() ---------------------------------
    155             # function: Format indent.
    156             # args: - $aText: text to indent
    157             # - $aLevel: Level.
    158             # - $aGroupLevel: Group level.
    159             # - $aAdd
    160             # - $aGlobalLevel
    161              
    162             sub _formatLevelIndent {
    163             # Get arguments
    164 571     571   1743 my ($self, $aText, $aAdd, $aGlobalLevel) = @_;
    165             # Local variables
    166 571         632 my ($levelIndent, $indent, $nrOfIndents);
    167             # Alias indentation option
    168 571         1029 $levelIndent = $self->{options}{'levelIndent'}; #=~ s/[0-9]+/&/;
    169             # Calculate number of indents
    170 571         1025 $nrOfIndents = abs($aGlobalLevel * 2 + $aAdd - 1) * $levelIndent;
    171             # Assemble indents
    172 571         1788 $indent = pack("A$nrOfIndents");
    173             # Return value
    174 571         1674 return $indent . $aText;
    175             } # _formatLevelIndent()
    176              
    177              
    178             #--- HTML::Toc::_formatToc() --------------------------------------------------
    179             # function: Format ToC.
    180             # args: - aPreviousLevel
    181             # - aPreviousGroupLevel
    182             # - aToc: ToC to format.
    183             # - aHeaderLines
    184             # - aGlobalLevel
    185             # - aLevelIndex
    186             # note: Recursive function this is.
    187              
    188             sub _formatToc {
    189             # Get arguments
    190             my (
    191 238     238   493 $self, $aPreviousLevel, $aPreviousGroupLevel, $aToc, $aHeaderLines,
    192             $aGlobalLevel, $aLevelIndex
    193             ) = @_;
    194             # Local variables
    195 238         335 my ($level, $groupLevel, $line, $groupId, $text, $compareStatus);
    196 0         0 my ($anchorName, $globalLevel, $node, $sequenceNr);
    197              
    198             LOOP: {
    199             # Lines need processing?
    200 238         272 while (scalar(@$aHeaderLines) > 0) {
      238         575  
    201             # Yes, lines need processing;
    202             # Get line
    203 414         636 $line = shift @$aHeaderLines;
    204            
    205             # Determine levels
    206 414         1899 ($level, $groupLevel, $groupId, $node, $sequenceNr,
    207             $anchorName, $text) = split(
    208             / /, $line, 7
    209             );
    210             # Must level and group be processed?
    211 414 100 100     4025 if (
    212             ($level =~ m/$self->{options}{'levelToToc'}/) &&
    213             ($groupId =~ m/$self->{options}{'groupToToc'}/)
    214             ) {
    215             # Yes, level must be processed;
    216             # Compare levels
    217 412         1145 $compareStatus = $self->_compareLevels(
    218             $level, $aPreviousLevel, $groupLevel, $aPreviousGroupLevel
    219             );
    220              
    221             COMPARE_LEVELS: {
    222              
    223             # Equals?
    224 412 100       483 if ($compareStatus == 0) {
      412         925  
    225             # Yes, levels are equal;
    226 230 100       470 if ($aLevelIndex) {
    227 86         4196 $$aToc .= eval($self->{_templateLevelClose});
    228             } # if
    229             # Format level
    230 66         188 $$aToc .= $self->_formatLevelIndent(
    231             ref($self->{_templateLevel}) eq "CODE" ?
    232 230 100       9210 &{$self->{_templateLevel}}(
    233             $level, $groupId, $node, $sequenceNr, $text
    234             ) :
    235             eval($self->{_templateLevel}),
    236             0, $aGlobalLevel
    237             );
    238 230         618 $aLevelIndex++;
    239             }
    240              
    241             # Greater?
    242 412 100       955 if ($compareStatus > 0) {
    243             # Yes, new level is greater than previous level;
    244             # Increase global level
    245 145 100       521 if ($aGlobalLevel++) {
    246 50         82 $$aToc .= "\n"
    247             } # if
    248             # Format begin of level
    249 145         8234 $$aToc .= $self->_formatLevelIndent(
    250             eval($self->{_templateLevelBegin}), -1, $aGlobalLevel
    251             );
    252             # Must level be single-stepped?
    253 145 100 100     1229 if (
          100        
    254             $self->{options}{'doSingleStepLevel'} &&
    255             ($aPreviousLevel) &&
    256             ($level > $aPreviousLevel)
    257             ) {
    258             # Yes, level must be single-stepped;
    259             # Make sure, new level is increased one step only
    260 36 100       101 if ($level > $aPreviousLevel + 1) {
    261 1         3 $level = $aPreviousLevel + 1;
    262 1         1 $text = '';
    263             # Format level
    264 0         0 $$aToc .= $self->_formatLevelIndent(
    265             ref($self->{_templateLevel}) eq "CODE" ?
    266 1 50       48 &{$self->{_templateLevel}}(
    267             $level, $groupId, $node, $sequenceNr, $text
    268             ) :
    269             eval($self->{_templateLevel}),
    270             0, $aGlobalLevel
    271             );
    272             } # if
    273             }
    274             # Process line again
    275 145         311 unshift @$aHeaderLines, $line;
    276             # Assemble TOC (recursive) for next level
    277 145         521 $self->_formatToc(
    278             $level, $groupLevel, $aToc, $aHeaderLines, $aGlobalLevel, 0
    279             );
    280             # Format end of level
    281 145         7880 $$aToc .= eval($self->{_templateLevelClose});
    282 145         6727 $$aToc .= $self->_formatLevelIndent(
    283             eval($self->{_templateLevelEnd}), -1, $aGlobalLevel
    284             );
    285             # Decrease global level
    286 145         431 $aGlobalLevel--;
    287             # Indent for line to come
    288 145 100 100     932 if (scalar(@$aHeaderLines) && $level > 1 || $aGlobalLevel) {
          100        
    289 50         133 $$aToc .= $self->_formatLevelIndent('', 0, $aGlobalLevel);
    290             } # if
    291             # Exit loop
    292 145         625 last COMPARE_LEVELS;
    293             }
    294              
    295             # Smaller?
    296 267 100       1192 if ($compareStatus < 0) {
    297             # Yes, new level is smaller than previous level;
    298             # Process line again
    299 37         76 unshift @$aHeaderLines, $line;
    300             # End loop
    301 37         109 last LOOP;
    302             }
    303             }
    304             }
    305             }
    306             }
    307             } # _formatToc()
    308              
    309              
    310             #--- HTML::Toc::_parseTokenGroups() -------------------------------------------
    311             # function: Parse token groups
    312              
    313             sub _parseTokenGroups {
    314             # Get arguments
    315 109     109   162 my ($self) = @_;
    316             # Local variables
    317 109         452 my ($group, $levelGroups, $numberingStyle);
    318              
    319             # Clear any previous 'levelGroups'
    320 109         185 $self->{_levelGroups} = undef;
    321             # Determine default 'numberingStyle'
    322 109 100       533 $numberingStyle = defined($self->{options}{'numberingStyle'}) ?
    323             $self->{options}{'numberingStyle'} : NUMBERING_STYLE_DECIMAL;
    324              
    325             # Loop through groups
    326 109         149 foreach $group (@{$self->{options}{'tokenToToc'}}) {
      109         288  
    327             # 'groupId' is specified?
    328 514 100       1083 if (! defined($group->{'groupId'})) {
    329             # No, 'groupId' isn't specified;
    330             # Set default groupId
    331 168         274 $group->{'groupId'} = GROUP_ID_H;
    332             }
    333             # 'level' is specified?
    334 514 100       1003 if (! defined($group->{'level'})) {
    335             # No, 'level' isn't specified;
    336             # Set default level
    337 25         44 $group->{'level'} = LEVEL_1;
    338             }
    339             # 'numberingStyle' is specified?
    340 514 100       1011 if (! defined($group->{'numberingStyle'})) {
    341             # No, 'numberingStyle' isn't specified;
    342             # Set default numberingStyle
    343 189         278 $group->{'numberingStyle'} = $numberingStyle;
    344             }
    345             # Add group to '_levelGroups' variabele
    346 514         1579 $self->{_levelGroups}{$group->{'groupId'}}[$group->{'level'} - 1] =
    347             $group;
    348             }
    349             } # _parseTokenGroups()
    350              
    351              
    352             #--- HTML::Toc::_setDefaults() ------------------------------------------------
    353             # function: Set default options.
    354              
    355             sub _setDefaults {
    356             # Get arguments
    357 48     48   84 my ($self) = @_;
    358             # Set default options
    359 48         1174 $self->setOptions(
    360             {
    361             'attributeToExcludeToken' => '-',
    362             'attributeToTocToken' => '@',
    363             'insertionPoint' => 'after ',
    364             'levelToToc' => '.*',
    365             'groupToToc' => '.*',
    366             'doNumberToken' => 0,
    367             'doLinkToFile' => 0,
    368             'doLinkToToken' => 1,
    369             'doLinkToId' => 0,
    370             'doSingleStepLevel' => 1,
    371             'linkUri' => '',
    372             'levelIndent' => 3,
    373             'doNestGroup' => 0,
    374             'doUseExistingAnchors' => 1,
    375             'doUseExistingIds' => 1,
    376             'tokenToToc' => [
    377             {
    378             'level' => 1,
    379             'tokenBegin' => '

    '

    380             }, {
    381             'level' => 2,
    382             'tokenBegin' => '

    '

    383             }, {
    384             'level' => 3,
    385             'tokenBegin' => '

    '

    386             }, {
    387             'level' => 4,
    388             'tokenBegin' => '

    '

    389             }, {
    390             'level' => 5,
    391             'tokenBegin' => '
    '
    392             }, {
    393             'level' => 6,
    394             'tokenBegin' => '
    '
    395             }
    396             ],
    397             'header' =>
    398             "\n\n",
    399             'footer' =>
    400             "\n\n",
    401             }
    402             );
    403             } # _setDefaults()
    404              
    405              
    406             #--- HTML::Toc::clear() -------------------------------------------------------
    407             # function: Clear ToC.
    408              
    409             sub clear {
    410             # Get arguments
    411 97     97 1 180 my ($self) = @_;
    412             # Clear ToC
    413 97         186 $self->{_toc} = "";
    414 97         186 $self->{toc} = "";
    415 97         1412 $self->{groupIdLevels} = undef;
    416 97         545 $self->{levels} = undef;
    417             } # clear()
    418              
    419              
    420             #--- HTML::Toc::format() ------------------------------------------------------
    421             # function: Format ToC.
    422             # returns: Formatted ToC.
    423              
    424             sub format {
    425             # Get arguments
    426 93     93 1 205 my ($self) = @_;
    427             # Local variables;
    428 93         141 my $toc = "";
    429 93         840 my @tocLines = split(/\r\n|\n/, $self->{_toc});
    430             # Format table of contents
    431 93         432 $self->_formatToc("0", "0", \$toc, \@tocLines, 0, 0);
    432             # Remove last newline
    433             # $toc =~ s/\r\n$//m;
    434             # $toc =~ s/\r$//m;
    435 93         502 $toc =~ s/\n$//m;
    436             # Add header & footer
    437 93         394 $toc = $self->{options}{'header'} . $toc . $self->{options}{'footer'};
    438             # Return value
    439 93         534 return $toc;
    440             } # format()
    441              
    442              
    443             #--- HTML::Toc::parseOptions() ------------------------------------------------
    444             # function: Parse options.
    445              
    446             sub parseOptions {
    447             # Get arguments
    448 109     109 0 172 my ($self) = @_;
    449             # Alias options
    450 109         187 my $options = $self->{options};
    451              
    452             # Parse token groups
    453 109         293 $self->_parseTokenGroups();
    454              
    455             # Link ToC to tokens?
    456 109 100       406 if ($self->{options}{'doLinkToToken'}) {
    457             # Yes, link ToC to tokens;
    458             # Determine anchor href template begin
    459 77 100       373 $self->{_templateAnchorHrefBegin} =
        100          
    460             defined($options->{'templateAnchorHrefBegin'}) ?
    461             $options->{'templateAnchorHrefBegin'} :
    462             $options->{'doLinkToFile'} ?
    463             TEMPLATE_ANCHOR_HREF_BEGIN_FILE : TEMPLATE_ANCHOR_HREF_BEGIN;
    464              
    465             # Determine anchor href template end
    466 77 50       266 $self->{_templateAnchorHrefEnd} =
    467             defined($options->{'templateAnchorHrefEnd'}) ?
    468             $options->{'templateAnchorHrefEnd'} :
    469             TEMPLATE_ANCHOR_HREF_END;
    470              
    471             # Determine anchor name template
    472 77 100       395 $self->{_templateAnchorName} =
    473             defined($options->{'templateAnchorName'}) ?
    474             $options->{'templateAnchorName'} :
    475             TEMPLATE_ANCHOR_NAME;
    476              
    477             # Determine anchor name template begin
    478 77 100       264 $self->{_templateAnchorNameBegin} =
    479             defined($options->{'templateAnchorNameBegin'}) ?
    480             $options->{'templateAnchorNameBegin'} :
    481             TEMPLATE_ANCHOR_NAME_BEGIN;
    482              
    483             # Determine anchor name template end
    484 77 100       304 $self->{_templateAnchorNameEnd} =
    485             defined($options->{'templateAnchorNameEnd'}) ?
    486             $options->{'templateAnchorNameEnd'} :
    487             TEMPLATE_ANCHOR_NAME_END;
    488             }
    489              
    490             # Determine token number template
    491 109 100       335 $self->{_templateTokenNumber} =
    492             defined($options->{'templateTokenNumber'}) ?
    493             $options->{'templateTokenNumber'} :
    494             TEMPLATE_TOKEN_NUMBER;
    495              
    496             # Determine level template
    497 109 100       322 $self->{_templateLevel} =
    498             defined($options->{'templateLevel'}) ?
    499             $options->{'templateLevel'} :
    500             TEMPLATE_LEVEL;
    501              
    502             # Determine level begin template
    503 109 100       404 $self->{_templateLevelBegin} =
    504             defined($options->{'templateLevelBegin'}) ?
    505             $options->{'templateLevelBegin'} :
    506             TEMPLATE_LEVEL_BEGIN;
    507              
    508             # Determine level close template
    509 109 50       388 $self->{_templateLevelClose} =
    510             defined($options->{'templateLevelClose'}) ?
    511             $options->{'templateLevelClose'} :
    512             TEMPLATE_LEVEL_CLOSE;
    513              
    514             # Determine level end template
    515 109 100       329 $self->{_templateLevelEnd} =
    516             defined($options->{'templateLevelEnd'}) ?
    517             $options->{'templateLevelEnd'} :
    518             TEMPLATE_LEVEL_END;
    519              
    520             # Determine 'anchor name begin' begin update token
    521 109 100       329 $self->{_tokenUpdateBeginOfAnchorNameBegin} =
    522             defined($options->{'tokenUpdateBeginOfAnchorNameBegin'}) ?
    523             $options->{'tokenUpdateBeginOfAnchorNameBegin'} :
    524             TOKEN_UPDATE_BEGIN_OF_ANCHOR_NAME_BEGIN;
    525              
    526             # Determine 'anchor name begin' end update token
    527 109 100       345 $self->{_tokenUpdateEndOfAnchorNameBegin} =
    528             defined($options->{'tokenUpdateEndOfAnchorNameBegin'}) ?
    529             $options->{'tokenUpdateEndOfAnchorNameBegin'} :
    530             TOKEN_UPDATE_END_OF_ANCHOR_NAME_BEGIN;
    531              
    532             # Determine 'anchor name end' begin update token
    533 109 100       453 $self->{_tokenUpdateBeginOfAnchorNameEnd} =
    534             defined($options->{'tokenUpdateBeginOfAnchorNameEnd'}) ?
    535             $options->{'tokenUpdateBeginOfAnchorNameEnd'} :
    536             TOKEN_UPDATE_BEGIN_OF_ANCHOR_NAME_END;
    537              
    538             # Determine 'anchor name end' end update token
    539 109 100       301 $self->{_tokenUpdateEndOfAnchorNameEnd} =
    540             defined($options->{'tokenUpdateEndOfAnchorNameEnd'}) ?
    541             $options->{'tokenUpdateEndOfAnchorNameEnd'} :
    542             TOKEN_UPDATE_END_OF_ANCHOR_NAME_END;
    543              
    544             # Determine number begin update token
    545 109 100       339 $self->{_tokenUpdateBeginNumber} =
    546             defined($options->{'tokenUpdateBeginNumber'}) ?
    547             $options->{'tokenUpdateBeginNumber'} :
    548             TOKEN_UPDATE_BEGIN_NUMBER;
    549              
    550             # Determine number end update token
    551 109 100       293 $self->{_tokenUpdateEndNumber} =
    552             defined($options->{'tokenUpdateEndNumber'}) ?
    553             $options->{'tokenUpdateEndNumber'} :
    554             TOKEN_UPDATE_END_NUMBER;
    555              
    556             # Determine toc begin update token
    557 109 100       335 $self->{_tokenUpdateBeginToc} =
    558             defined($options->{'tokenUpdateBeginToc'}) ?
    559             $options->{'tokenUpdateBeginToc'} :
    560             TOKEN_UPDATE_BEGIN_TOC;
    561              
    562             # Determine toc end update token
    563 109 100       686 $self->{_tokenUpdateEndToc} =
    564             defined($options->{'tokenUpdateEndToc'}) ?
    565             $options->{'tokenUpdateEndToc'} :
    566             TOKEN_UPDATE_END_TOC;
    567              
    568             } # parseOptions()
    569              
    570              
    571             #--- HTML::Toc::setOptions() --------------------------------------------------
    572             # function: Set options.
    573             # args: - aOptions: Reference to hash containing options.
    574              
    575             sub setOptions {
    576             # Get arguments
    577 142     142 0 102999 my ($self, $aOptions) = @_;
    578             # Add options
    579 142         422 %{$self->{options}} = (%{$self->{options}}, %$aOptions);
      142         2395  
      142         1034  
    580             } # setOptions()
    581              
    582              
    583             1;