#==== HTML::TocUpdator ======================================================== # function: Update 'HTML::Toc' table of contents. # note: - 'TUT' is an abbreviation of 'Toc Update Token'. package HTML::TocUpdator; use strict; use HTML::TocInsertor; BEGIN { use vars qw(@ISA $VERSION); $VERSION = '0.91'; @ISA = qw(HTML::TocInsertor); } use constant TUT_TOKENTYPE_START => 0; use constant TUT_TOKENTYPE_END => 1; use constant TUT_TOKENTYPE_TEXT => 2; use constant TUT_TOKENTYPE_COMMENT => 3; use constant MODE_DO_NOTHING => 0; # 0b00 use constant MODE_DO_INSERT => 1; # 0b01 use constant MODE_DO_UPDATE => 3; # 0b11 END {} #--- HTML::TocUpdator::new() -------------------------------------------------- # function: Constructor. sub new { # Get arguments my ($aType) = @_; my $self = $aType->SUPER::new; # Bias to not update ToC $self->{htu__Mode} = MODE_DO_NOTHING; # Bias to not delete tokens $self->{_doDeleteTokens} = 0; # Reset batch variables #$self->_resetBatchVariables; $self->{options} = {}; # TODO: Initialize output return $self; } # new() #--- HTML::TocUpdator::_deinitializeUpdatorBatch() -------------------------- # function: Deinitialize updator batch. # args: - $aTocs: Reference to array of tocs. sub _deinitializeUpdatorBatch { # Get arguments my ($self, $aTocs) = @_; # Indicate end of ToC updating $self->{htu__Mode} = MODE_DO_NOTHING; # Deinitialize insertor batch $self->_deinitializeInsertorBatch(); } # _deinitializeUpdatorBatch() #--- HTML::TokenUpdator::_doesHashEqualHash() --------------------------------- # function: Determines whether hash1 equals hash2. # args: - $aHash1 # - $aHash2 # returns: True (1) if hash1 equals hash2, 0 if not. For example, with the # following hashes: # # %hash1 = { %hash2 = { # 'class' => 'header', 'class' => 'header', # 'id' => 'intro1' 'id' => 'intro2' # } } # # the routine will return 0, cause the hash fields 'id' differ. # note: Class function. sub _doesHashEqualHash { # Get arguments my ($aHash1, $aHash2) = @_; # Local variables my ($key1, $value1, $key2, $value2, $result); # Bias to success $result = 1; # Loop through hash1 while values available HASH1: while (($key1, $value1) = each %$aHash1) { # Yes, values are available; # Value1 differs from value2? if ($value1 ne $aHash2->{$key1}) { # Yes, hashes differ; # Indicate condition fails $result = 0; # Reset 'each' iterator which we're going to break keys %$aHash2; # Break loop last HASH1; } } # Return value return $result; } # _doesHashEqualHash() #--- HTML::TokenUpdator::_doesTagExistInArray() ------------------------------- # function: Check whether tag & attributes matches any of the tags & attributes # in the specified array. The array must consist of elements with # format: # # [$tag, \%attributes] # # args: - $aTag: tag to search for # - $aAttributes: tag attributes to search for # - $aArray: Array to search in. # returns: 1 if tag does exist in array, 0 if not. # note: Class function. sub _doesTagExistInArray { # Get arguments my ($aTag, $aAttributes, $aArray) = @_; # Local variables my ($tag, $result); # Bias to non-existing tag $result = 0; # Loop through existing tags TAG: foreach $tag (@{$aArray}) { if (defined(@{$tag}[0])) { # Does tag equals any existing tag? if ($aTag eq @{$tag}[0]) { # Yes, tag equals existing tag; # Do hashes equal? if (HTML::TocUpdator::_doesHashEqualHash( $aAttributes, @{$tag}[1] )) { # Yes, hashes are the same; # Indicate tag exists in array $result = 1; # Break loop last TAG; } } } } # Return value return $result; } # _doesTagExistInArray() #--- HTML::TocUpdator::_initializeUpdatorBatch() ---------------------------- # function: Initialize insertor batch. # args: - $aMode: Mode. Can be either MODE_DO_INSERT or MODE_DO_UPDATE # - $aTocs: Reference to array of tocs. # - $aOptions: optional options # note: Updating actually means: deleting the old ToC and inserting a new # ToC. That's why we're calling 'insertor' methods here. sub _initializeUpdatorBatch { # Get arguments my ($self, $aMode, $aTocs, $aOptions) = @_; # Initialize insertor batch $self->_initializeInsertorBatch($aTocs, $aOptions); # Parse ToC update templates $self->_parseTocUpdateTokens(); # Indicate start of ToC updating $self->{htu__Mode} = $aMode; } # _initializeUpdatorBatch() #--- HTML::TocUpdator::_parseTocUpdateTokens() -------------------------------- # function: Parse ToC insertion point specifier. sub _parseTocUpdateTokens { # Get arguments my ($self) = @_; # Local variables my ($toc, $tokenType, $tokenPreposition, $token); my ($tocInsertionPoint, $tocInsertionPointTokenAttributes); # Create parser for update begin tokens my $tokenUpdateBeginParser = HTML::_TokenUpdateParser->new( $self->{_tokensUpdateBegin} ); # Create parser for update end tokens my $tokenUpdateEndParser = HTML::_TokenUpdateParser->new( $self->{_tokensUpdateEnd} ); # Loop through ToCs foreach $toc (@{$self->{_tocs}}) { # Parse update tokens $tokenUpdateBeginParser->parse( $toc->{_tokenUpdateBeginOfAnchorNameBegin} ); $tokenUpdateBeginParser->parse($toc->{_tokenUpdateBeginOfAnchorNameEnd}); $tokenUpdateBeginParser->parse($toc->{_tokenUpdateBeginNumber}); $tokenUpdateBeginParser->parse($toc->{_tokenUpdateBeginToc}); $tokenUpdateEndParser->parse($toc->{_tokenUpdateEndOfAnchorNameBegin}); $tokenUpdateEndParser->parse($toc->{_tokenUpdateEndOfAnchorNameEnd}); $tokenUpdateEndParser->parse($toc->{_tokenUpdateEndNumber}); $tokenUpdateEndParser->parse($toc->{_tokenUpdateEndToc}); } } # _parseTocUpdateTokens() #--- HTML::TocUpdator::_resetBatchVariables() --------------------------------- # function: Reset batch variables sub _resetBatchVariables { # Get arguments my ($self) = @_; # Call ancestor $self->SUPER::_resetBatchVariables(); # Arrays containing start, end, comment & text tokens which indicate # the begin of ToC tokens. The tokens are stored in keys of hashes to # avoid storing duplicates as an array would. $self->{_tokensUpdateBegin} = [ [], # ['<start tag>', <attributes>] {}, # {'<end tag>' => ''} {}, # {'<text>' => ''} {} # {'<comment>' => ''} ]; # Arrays containing start, end, comment & text tokens which indicate # the end of ToC tokens. The tokens are stored in keys of hashes to # avoid storing duplicates as an array would. $self->{_tokensUpdateEnd} = [ [], # ['<start tag>', <attributes>] {}, # {'<end tag>' => ''} {}, # {'<text>' => ''} {} # {'<comment>' => ''} ]; } # _resetBatchVariables() #--- HTML::TocUpdator::_setActiveAnchorName() --------------------------------- # function: Set active anchor name. # args: - aAnchorName: Name of anchor name to set active. sub _setActiveAnchorName { # Get arguments my ($self, $aAnchorName) = @_; # Are tokens being deleted? if (! $self->{_doDeleteTokens}) { # No, tokens aren't being deleted; # Call ancestor to set anchor name $self->SUPER::_setActiveAnchorName($aAnchorName); } } # _setActiveAnchorName() #--- HTML::TocUpdator::_update() ---------------------------------------------- # function: Update ToC in string. # args: - $aMode: Mode. Can be either MODE_DO_UPDATE or MODE_DO_INSERT. # - $aToc: (reference to array of) ToC object to update # - $aString: string to update ToC of # - $aOptions: optional updator options # note: Used internally. sub _update { # Get arguments my ($self, $aMode, $aToc, $aString, $aOptions) = @_; # Initialize TocUpdator batch $self->_initializeUpdatorBatch($aMode, $aToc, $aOptions); # Start updating ToC by starting ToC insertion $self->_insert($aString); # Deinitialize TocUpdator batch $self->_deinitializeUpdatorBatch(); } # update() #--- HTML::TocUpdator::_updateFile() ------------------------------------------ # function: Update ToCs in file. # args: - $aMode: Mode. Can be either MODE_DO_UPDATE or MODE_DO_INSERT. # - $aToc: (reference to array of) ToC object to update # - $aFile: (reference to array of) file to parse for updating. # - $aOptions: optional updator options # note: Used internally. sub _updateFile { # Get arguments my ($self, $aMode, $aToc, $aFile, $aOptions) = @_; # Initialize TocUpdator batch $self->_initializeUpdatorBatch($aMode, $aToc, $aOptions); # Start updating ToC by starting ToC insertion $self->_insertIntoFile($aFile); # Deinitialize TocUpdator batch $self->_deinitializeUpdatorBatch(); } # _updateFile() #--- HTML::TocUpdator::_writeOrBufferOutput() --------------------------------- # function: Write processed HTML to output device(s). # args: - aOutput: scalar to write sub _writeOrBufferOutput { # Get arguments my ($self, $aOutput) = @_; # Delete output? if (! $self->{_doDeleteTokens}) { # No, don't delete output; # Call ancestor $self->SUPER::_writeOrBufferOutput($aOutput); } } # _writeOrBufferOutput() #--- HTML::TocUpdator::anchorNameBegin() -------------------------------------- # function: Process 'anchor name begin' generated by HTML::Toc. # args: - $aAnchorName: Anchor name begin tag to output. # - $aToc: Reference to ToC to which anchorname belongs. sub anchorNameBegin { # Get arguments my ($self, $aAnchorNameBegin, $aToc) = @_; # Call ancestor $self->SUPER::anchorNameBegin($aAnchorNameBegin); # Must ToC be inserted or updated? if ($self->{htu__Mode} != MODE_DO_NOTHING) { # Yes, ToC must be inserted or updated; # Surround anchor name with update tags $self->{_outputPrefix} = $aToc->{_tokenUpdateBeginOfAnchorNameBegin} . $self->{_outputPrefix} . $aToc->{_tokenUpdateEndOfAnchorNameBegin}; } } # anchorNameBegin() #--- HTML::TocUpdator::anchorNameEnd() ---------------------------------------- # function: Process 'anchor name end' generated by HTML::Toc. # args: - $aAnchorNameEnd: Anchor name end tag to output. # - $aToc: Reference to ToC to which anchorname belongs. sub anchorNameEnd { # Get arguments my ($self, $aAnchorNameEnd, $aToc) = @_; # Call ancestor $self->SUPER::anchorNameEnd($aAnchorNameEnd); # Must ToC be inserted or updated? if ($self->{htu__Mode} != MODE_DO_NOTHING) { # Yes, ToC must be inserted or updated; # Surround anchor name with update tags $self->{_outputSuffix} = $aToc->{_tokenUpdateBeginOfAnchorNameEnd} . $self->{_outputSuffix} . $aToc->{_tokenUpdateEndOfAnchorNameEnd}; } } # anchorNameEnd() #--- HTML::TocUpdator::comment() ---------------------------------------------- # function: Process comment. # args: - $aComment: comment text with '<!--' and '-->' tags stripped off. sub comment { # Get arguments my ($self, $aComment) = @_; # Must ToC be updated? if ($self->{htu__Mode} == MODE_DO_UPDATE) { # Yes, ToC must be updated; # Updator is currently deleting tokens? if ($self->{_doDeleteTokens}) { # Yes, tokens must be deleted; # Call ancestor $self->SUPER::comment($aComment); # Look for update end token # Does comment matches update end token? if (defined( $self->{_tokensUpdateEnd}[TUT_TOKENTYPE_COMMENT]{$aComment} )) { # Yes, comment matches update end token; # Indicate to stop deleting tokens $self->{_doDeleteTokens} = 0; } } else { # No, tokens mustn't be deleted; # Look for update begin token # Does comment matches update begin token? if (defined( $self->{_tokensUpdateBegin}[TUT_TOKENTYPE_COMMENT]{$aComment} )) { # Yes, comment matches update begin token; # Indicate to start deleting tokens $self->{_doDeleteTokens} = 1; } # Call ancestor $self->SUPER::comment($aComment); } } else { # No, ToC mustn't be updated; # Call ancestor $self->SUPER::comment($aComment); } } # comment() #--- HTML::TocUpdator::end() -------------------------------------------------- # function: This function is called every time a closing tag is encountered. # args: - $aTag: tag name (in lower case). # - $aOrigText: tag name including brackets. sub end { # Get arguments my ($self, $aTag, $aOrigText) = @_; # Call ancestor $self->SUPER::end($aTag, $aOrigText); # Must ToC be updated? if ($self->{htu__Mode} == MODE_DO_UPDATE) { # Yes, ToC must be updated; # Updator is currently deleting tokens? if ($self->{_doDeleteTokens}) { # Yes, tokens must be deleted; # Does end tag matches update end token? if (defined( $self->{_tokensUpdateEnd}[TUT_TOKENTYPE_END]{$aTag} )) { # Yes, end tag matches update end token; # Indicate to stop deleting tokens $self->{_doDeleteTokens} = 0; } } } } # end() #--- HTML::TocUpdator::insert() ----------------------------------------------- # function: Insert ToC in string. # args: - $aToc: (reference to array of) ToC object to update # - $aString: string to insert ToC in. # - $aOptions: optional updator options sub insert { # Get arguments my ($self, $aToc, $aString, $aOptions) = @_; # Do start insert $self->_update(MODE_DO_INSERT, $aToc, $aString, $aOptions); } # insert() #--- HTML::TocUpdator::insertIntoFile() -------------------------------------- # function: Insert ToC in file. # args: - $aToc: (reference to array of) ToC object to update # - $aFile: File to insert ToC in. # - $aOptions: optional updator options sub insertIntoFile { # Get arguments my ($self, $aToc, $aFile, $aOptions) = @_; # Do start insert $self->_updateFile(MODE_DO_INSERT, $aToc, $aFile, $aOptions); } # insertIntoFile() #--- HTML::TocUpdator::number() ----------------------------------------------- # function: Process heading number generated by HTML::Toc. # args: - $aNumber # - $aToc: Reference to ToC to which anchorname belongs. sub number { # Get arguments my ($self, $aNumber, $aToc) = @_; # Call ancestor $self->SUPER::number($aNumber); # Must ToC be inserted or updated? if ($self->{htu__Mode} != MODE_DO_NOTHING) { # Yes, ToC must be inserted or updated; # Surround number with update tags $self->{_outputSuffix} = $aToc->{_tokenUpdateBeginNumber} . $self->{_outputSuffix} . $aToc->{_tokenUpdateEndNumber}; } } # number() #--- HTML::TocUpdator::start() ------------------------------------------------ # function: This function is called every time an opening tag is encountered. # args: - $aTag: tag name (in lower case). # - $aAttr: reference to hash containing all tag attributes (in lower # case). # - $aAttrSeq: reference to array containing all tag attributes (in # lower case) in the original order # - $aOrigText: the original HTML text sub start { # Get arguments my ($self, $aTag, $aAttr, $aAttrSeq, $aOrigText) = @_; # Must ToC be updated? if ($self->{htu__Mode} == MODE_DO_UPDATE) { # Yes, ToC must be updated; # Does start tag matches token update begin tag? if (HTML::TocUpdator::_doesTagExistInArray( $aTag, $aAttr, $self->{_tokensUpdateBegin}[TUT_TOKENTYPE_START] )) { # Yes, start tag matches token update tag; # Indicate to delete tokens $self->{_doDeleteTokens} = 1; } } # Let ancestor process the start tag $self->SUPER::start($aTag, $aAttr, $aAttrSeq, $aOrigText); } # start() #--- HTML::TocUpdator::toc() -------------------------------------------------- # function: Toc processing method. Add toc reference to scenario. # args: - $aScenario: Scenario to add ToC reference to. # - $aToc: Reference to ToC to insert. # note: The ToC hasn't been build yet; only a reference to the ToC to be # build is inserted. sub toc { # Get arguments my ($self, $aScenario, $aToc) = @_; # Surround toc with update tokens # Add update begin token push(@$aScenario, \$aToc->{_tokenUpdateBeginToc}); # Call ancestor $self->SUPER::toc($aScenario, $aToc); # Add update end token push(@$aScenario, \$aToc->{_tokenUpdateEndToc}); } # toc() #--- HTML::TocUpdator::_processTocText() -------------------------------------- # function: Toc text processing function. # args: - $aText: Text to add to ToC. # - $aToc: ToC to add text to. sub _processTocText { # Get arguments my ($self, $aText, $aToc) = @_; # Delete output? if (! $self->{_doDeleteTokens}) { # No, don't delete output; # Call ancestor $self->SUPER::_processTocText($aText, $aToc); } } # _processTocText() #--- HTML::TocUpdator::update() ----------------------------------------------- # function: Update ToC in string. # args: - $aToc: (reference to array of) ToC object to update # - $aString: string to update ToC of # - $aOptions: optional updator options sub update { # Get arguments my ($self, $aToc, $aString, $aOptions) = @_; # Do start update $self->_update(MODE_DO_UPDATE, $aToc, $aString, $aOptions); } # update() #--- HTML::TocUpdator::updateFile() ------------------------------------------- # function: Update ToC of file. # args: - $aToc: (reference to array of) ToC object to update # - $aFile: (reference to array of) file to parse for updating. # - $aOptions: optional updator options sub updateFile { # Get arguments my ($self, $aToc, $aFile, $aOptions) = @_; # Do start update $self->_updateFile(MODE_DO_UPDATE, $aToc, $aFile, $aOptions); } # update() #=== HTML::_TokenUpdateParser ================================================= # function: Parse 'update tokens'. 'Update tokens' mark HTML code which is # inserted by 'HTML::TocInsertor'. # note: Used internally. package HTML::_TokenUpdateParser; BEGIN { use vars qw(@ISA); @ISA = qw(HTML::Parser); } END {} #--- HTML::_TokenUpdateParser::new() ------------------------------------------ # function: Constructor sub new { # Get arguments my ($aType, $aTokenArray) = @_; # Create instance my $self = $aType->SUPER::new; # Reference token array $self->{tokens} = $aTokenArray; # Return instance return $self; } # new() #--- HTML::_TokenUpdateParser::comment() -------------------------------------- # function: Process comment. # args: - $aComment: comment text with '<!--' and '-->' tags stripped off. sub comment { # Get arguments my ($self, $aComment) = @_; # Add token to array of update tokens $self->{tokens}[HTML::TocUpdator::TUT_TOKENTYPE_COMMENT]{$aComment} = ''; } # comment() #--- HTML::_TokenUpdateParser::end() ------------------------------------------ # function: This function is called every time a closing tag is encountered # by HTML::Parser. # args: - $aTag: tag name (in lower case). sub end { # Get arguments my ($self, $aTag, $aOrigText) = @_; # Add token to array of update tokens $self->{tokens}[HTML::TocUpdator::TUT_TOKENTYPE_END]{$aTag} = ''; } # end() #--- HTML::_TokenUpdateParser::parse() ---------------------------------------- # function: Parse token. # args: - $aToken: 'update token' to parse sub parse { # Get arguments my ($self, $aString) = @_; # Call ancestor $self->SUPER::parse($aString); } # parse() #--- HTML::_TokenUpdateParser::start() ---------------------------------------- # function: This function is called every time an opening tag is encountered. # args: - $aTag: tag name (in lower case). # - $aAttr: reference to hash containing all tag attributes (in lower # case). # - $aAttrSeq: reference to array containing all tag attributes (in # lower case) in the original order # - $aOrigText: the original HTML text sub start { # Get arguments my ($self, $aTag, $aAttr, $aAttrSeq, $aOrigText) = @_; # Does token exist in array? if (! HTML::TocUpdator::_doesTagExistInArray( $aTag, $aAttr, $self->{tokens}[HTML::TocUpdator::TUT_TOKENTYPE_START] )) { # No, token doesn't exist in array; # Add token to array of update tokens push( @{$self->{tokens}[HTML::TocUpdator::TUT_TOKENTYPE_START]}, [$aTag, $aAttr] ); } } # start() #--- HTML::_TokenUpdateParser::text() ----------------------------------------- # function: This function is called every time plain text is encountered. # args: - @_: array containing data. sub text { # Get arguments my ($self, $aText) = @_; # Add token to array of update tokens $self->{tokens}[HTML::TocUpdator::TUT_TOKENTYPE_TEXT]{$aText} = ''; } # text() 1;