############################################################################### # # Class: NaturalDocs::SymbolTable::IndexElement # ############################################################################### # # A class representing part of an indexed symbol. # ############################################################################### # This file is part of Natural Docs, which is Copyright (C) 2003-2008 Greg Valure # Natural Docs is licensed under the GPL use Tie::RefHash; use strict; use integer; package NaturalDocs::SymbolTable::IndexElement; # # Topic: How IndexElements Work # # This is a little tricky, so make sure you understand this. Indexes are sorted by symbol, then packages, then file. If there is only # one package for a symbol, or one file definition for a package/symbol, they are added inline to the entry. However, if there are # multiple packages or files, the function for it returns an arrayref of IndexElements instead. Which members are defined and # undefined should follow common sense. For example, if a symbol is defined in multiple packages, the symbol's IndexElement # will not define , , or ; those will be defined in child elements. Similarly, the child elements will # not define since it's redundant. # # Diagrams may be clearer. If a member isn't listed for an element, it isn't defined. # # A symbol that only has one package and file: # > [Element] # > - Symbol # > - Package # > - File # > - Type # > - Prototype # > - Summary # # A symbol that is defined by multiple packages, each with only one file: # > [Element] # > - Symbol # > - Package # > [Element] # > - Package # > - File # > - Type # > - Prototype # > - Summary # > [Element] # > - ... # # A symbol that is defined by one package, but has multiple files # > [Element] # > - Symbol # > - Package # > - File # > [Element] # > - File # > - Type # > - Protype # > - Summary # > [Element] # > - ... # # A symbol that is defined by multiple packages which have multiple files: # > [Element] # > - Symbol # > - Package # > [Element] # > - Package # > - File # > [Element] # > - File # > - Type # > - Prototype # > - Summary # > [Element] # > - ... # > [Element] # > - ... # # Why is it done this way?: # # Because it makes it easier to generate nice indexes since all the splitting and combining is done for you. If a symbol # has only one package, you just want to link to it, you don't want to break out a subindex for just one package. However, if # it has multiple package, you do want the subindex and to link to each one individually. Use and # to determine whether you need to add a subindex for it. # # # Combining Properties: # # All IndexElements also have combining properties set. # # CombinedType - The general of the entry. Conflicts combine into . # PackageSeparator - The package separator symbol of the entry. Conflicts combine into a dot. # # So if an IndexElement only has one definition, is the same as the and # is that of the definition's language. If other definitions are added and they have the same properties, the combined properties # will remain the same. However, if they're different, they switch values as noted above. # # # Sortable Symbol: # # is a pseudo-combining property. There were a few options for dealing with multiple languages defining # the same symbol but stripping different prefixes off it, but ultimately I decided to go with whatever the language does that # has the most definitions. There's not likely to be many conflicts here in the real world; probably the only thing would be # defining it in a text file and forgetting to specify the prefixes to strip there too. So this works. # # Ties are broken pretty much randomly, except that text files always lose if its one of the options. # # It's a pseudo-combining property because it's done after the IndexElements are all filled in and only stored in the top-level # ones. # ############################################################################### # Group: Implementation # # Constants: Members # # The class is implemented as a blessed arrayref. The following constants are its members. # # SYMBOL - The without the package portion. # PACKAGE - The package . Will be a package , undef for global, or an arrayref of # objects if multiple packages define the symbol. # FILE - The the package/symbol is defined in. Will be the file name or an arrayref of # if multiple files define the package/symbol. # TYPE - The package/symbol/file . # PROTOTYPE - The package/symbol/file prototype, or undef if not applicable. # SUMMARY - The package/symbol/file summary, or undef if not applicable. # COMBINED_TYPE - The combined of the element. # PACKAGE_SEPARATOR - The combined package separator symbol of the element. # SORTABLE_SYMBOL - The sortable symbol as a text string. # IGNORED_PREFIX - The part of the symbol that was stripped off to make the sortable symbol. # use NaturalDocs::DefineMembers 'SYMBOL', 'Symbol()', 'PACKAGE', 'Package()', 'FILE', 'File()', 'TYPE', 'Type()', 'PROTOTYPE', 'Prototype()', 'SUMMARY', 'Summary()', 'COMBINED_TYPE', 'CombinedType()', 'PACKAGE_SEPARATOR', 'PackageSeparator()', 'SORTABLE_SYMBOL', 'SortableSymbol()', 'IGNORED_PREFIX', 'IgnoredPrefix()'; # DEPENDENCY: New() depends on the order of these constants and that there is no inheritance.. ############################################################################### # Group: Modification Functions # # Function: New # # Returns a new object. # # This should only be used for creating an entirely new symbol. You should *not* pass arrayrefs as package or file parameters # if you are calling this externally. Use instead. # # Parameters: # # symbol - The without the package portion. # package - The package , or undef for global. # file - The symbol's definition file. # type - The symbol's . # prototype - The symbol's prototype, if applicable. # summary - The symbol's summary, if applicable. # # Optional Parameters: # # These parameters don't need to be specified. You should ignore them when calling this externally. # # combinedType - The symbol's combined . # packageSeparator - The symbol's combined package separator symbol. # sub New #(symbol, package, file, type, prototype, summary, combinedType, packageSeparator) { # DEPENDENCY: This depends on the parameter list being in the same order as the constants. my $self = shift; my $object = [ @_ ]; bless $object, $self; if (!defined $object->[COMBINED_TYPE]) { $object->[COMBINED_TYPE] = $object->[TYPE]; }; if (!defined $object->[PACKAGE_SEPARATOR]) { if ($object->[TYPE] eq ::TOPIC_FILE()) { $object->[PACKAGE_SEPARATOR] = '.'; } else { $object->[PACKAGE_SEPARATOR] = NaturalDocs::Languages->LanguageOf($object->[FILE])->PackageSeparator(); }; }; return $object; }; # # Function: Merge # # Adds another definition of the same symbol. Perhaps it has a different package or defining file. # # Parameters: # # package - The package , or undef for global. # file - The symbol's definition file. # type - The symbol's . # prototype - The symbol's protoype if applicable. # summary - The symbol's summary if applicable. # sub Merge #(package, file, type, prototype, summary) { my ($self, $package, $file, $type, $prototype, $summary) = @_; # If there's only one package... if (!$self->HasMultiplePackages()) { # If there's one package and it's the same as the new one... if ($package eq $self->Package()) { $self->MergeFile($file, $type, $prototype, $summary); } # If there's one package and the new one is different... else { my $selfDefinition = NaturalDocs::SymbolTable::IndexElement->New(undef, $self->Package(), $self->File(), $self->Type(), $self->Prototype(), $self->Summary(), $self->CombinedType(), $self->PackageSeparator()); my $newDefinition = NaturalDocs::SymbolTable::IndexElement->New(undef, $package, $file, $type, $prototype, $summary); $self->[PACKAGE] = [ $selfDefinition, $newDefinition ]; $self->[FILE] = undef; $self->[TYPE] = undef; $self->[PROTOTYPE] = undef; $self->[SUMMARY] = undef; if ($newDefinition->Type() ne $self->CombinedType()) { $self->[COMBINED_TYPE] = ::TOPIC_GENERAL(); }; if ($newDefinition->PackageSeparator() ne $self->PackageSeparator()) { $self->[PACKAGE_SEPARATOR] = '.'; }; }; } # If there's more than one package... else { # See if the new package is one of them. my $selfPackages = $self->Package(); my $matchingPackage; foreach my $testPackage (@$selfPackages) { if ($package eq $testPackage->Package()) { $testPackage->MergeFile($file, $type, $prototype, $summary);; return; }; }; my $newDefinition = NaturalDocs::SymbolTable::IndexElement->New(undef, $package, $file, $type, $prototype, $summary); push @{$self->[PACKAGE]}, $newDefinition; if ($newDefinition->Type() ne $self->CombinedType()) { $self->[COMBINED_TYPE] = ::TOPIC_GENERAL(); }; if ($newDefinition->PackageSeparator() ne $self->PackageSeparator()) { $self->[PACKAGE_SEPARATOR] = '.'; }; }; }; # # Function: Sort # # Sorts the package and file lists of the symbol. # sub Sort { my $self = shift; if ($self->HasMultipleFiles()) { @{$self->[FILE]} = sort { ::StringCompare($a->File(), $b->File()) } @{$self->File()}; } elsif ($self->HasMultiplePackages()) { @{$self->[PACKAGE]} = sort { ::StringCompare( $a->Package(), $b->Package()) } @{$self->[PACKAGE]}; foreach my $packageElement ( @{$self->[PACKAGE]} ) { if ($packageElement->HasMultipleFiles()) { $packageElement->Sort(); }; }; }; }; # # Function: MakeSortableSymbol # # Generates and . Should only be called after everything is merged. # sub MakeSortableSymbol { my $self = shift; my $finalLanguage; if ($self->HasMultiplePackages() || $self->HasMultipleFiles()) { # Collect all the files that define this symbol. my @files; if ($self->HasMultipleFiles()) { my $fileElements = $self->File(); foreach my $fileElement (@$fileElements) { push @files, $fileElement->File(); }; } else # HasMultiplePackages { my $packages = $self->Package(); foreach my $package (@$packages) { if ($package->HasMultipleFiles()) { my $fileElements = $package->File(); foreach my $fileElement (@$fileElements) { push @files, $fileElement->File(); }; } else { push @files, $package->File(); }; }; }; # Determine which language defines it the most. # Keys are language objects, values are counts. my %languages; tie %languages, 'Tie::RefHash'; foreach my $file (@files) { my $language = NaturalDocs::Languages->LanguageOf($file); if (exists $languages{$language}) { $languages{$language}++; } else { $languages{$language} = 1; }; }; my $topCount = 0; my @topLanguages; while (my ($language, $count) = each %languages) { if ($count > $topCount) { $topCount = $count; @topLanguages = ( $language ); } elsif ($count == $topCount) { push @topLanguages, $language; }; }; if (scalar @topLanguages == 1) { $finalLanguage = $topLanguages[0]; } else { if ($topLanguages[0]->Name() ne 'Text File') { $finalLanguage = $topLanguages[0]; } else { $finalLanguage = $topLanguages[1]; }; }; } else # !hasMultiplePackages && !hasMultipleFiles { $finalLanguage = NaturalDocs::Languages->LanguageOf($self->File()); }; my $textSymbol = NaturalDocs::SymbolString->ToText($self->Symbol(), $self->PackageSeparator()); my $ignoredPrefixLength = $finalLanguage->IgnoredPrefixLength($textSymbol, $self->CombinedType()); if ($ignoredPrefixLength) { $self->[IGNORED_PREFIX] = substr($textSymbol, 0, $ignoredPrefixLength); $self->[SORTABLE_SYMBOL] = substr($textSymbol, $ignoredPrefixLength); } else { $self->[SORTABLE_SYMBOL] = $textSymbol; }; }; ############################################################################### # # Functions: Information Functions # # Symbol - Returns the without the package portion. # Package - If is true, returns an arrayref of objects. # Otherwise returns the package , or undef if global. # File - If is true, returns an arrayref of objects. Otherwise # returns the name of the definition file. # Type - Returns the of the package/symbol/file, if applicable. # Prototype - Returns the prototype of the package/symbol/file, if applicable. # Summary - Returns the summary of the package/symbol/file, if applicable. # CombinedType - Returns the combined of the element. # PackageSeparator - Returns the combined package separator symbol of the element. # SortableSymbol - Returns the sortable symbol as a text string. Only available after calling . # IgnoredPrefix - Returns the part of the symbol that was stripped off to make the , or undef if none. # Only available after calling . # # Function: HasMultiplePackages # Returns whether is broken out into more elements. sub HasMultiplePackages { return ref($_[0]->[PACKAGE]); }; # Function: HasMultipleFiles # Returns whether is broken out into more elements. sub HasMultipleFiles { return ref($_[0]->[FILE]); }; ############################################################################### # Group: Support Functions # # Function: MergeFile # # Adds another definition of the same package/symbol. Perhaps the file is different. # # Parameters: # # file - The package/symbol's definition file. # type - The package/symbol's . # prototype - The package/symbol's protoype if applicable. # summary - The package/symbol's summary if applicable. # sub MergeFile #(file, type, prototype, summary) { my ($self, $file, $type, $prototype, $summary) = @_; # If there's only one file... if (!$self->HasMultipleFiles()) { # If there's one file and it's the different from the new one... if ($file ne $self->File()) { my $selfDefinition = NaturalDocs::SymbolTable::IndexElement->New(undef, undef, $self->File(), $self->Type(), $self->Prototype(), $self->Summary(), $self->CombinedType(), $self->PackageSeparator()); my $newDefinition = NaturalDocs::SymbolTable::IndexElement->New(undef, undef, $file, $type, $prototype, $summary); $self->[FILE] = [ $selfDefinition, $newDefinition ]; $self->[TYPE] = undef; $self->[PROTOTYPE] = undef; $self->[SUMMARY] = undef; if ($newDefinition->Type() ne $self->CombinedType()) { $self->[COMBINED_TYPE] = ::TOPIC_GENERAL(); }; if ($newDefinition->PackageSeparator() ne $self->PackageSeparator()) { $self->[PACKAGE_SEPARATOR] = '.'; }; } # If the file was the same, just ignore the duplicate in the index. } # If there's more than one file... else { # See if the new file is one of them. my $files = $self->File(); foreach my $testElement (@$files) { if ($testElement->File() eq $file) { # If the new file's already in the index, ignore the duplicate. return; }; }; my $newDefinition = NaturalDocs::SymbolTable::IndexElement->New(undef, undef, $file, $type, $prototype, $summary); push @{$self->[FILE]}, $newDefinition; if ($newDefinition->Type() ne $self->CombinedType()) { $self->[COMBINED_TYPE] = ::TOPIC_GENERAL(); }; if ($newDefinition->PackageSeparator() ne $self->PackageSeparator()) { $self->[PACKAGE_SEPARATOR] = '.'; }; }; }; 1;