<html><head><meta name="color-scheme" content="light dark"></head><body><pre style="word-wrap: break-word; white-space: pre-wrap;">###############################################################################
#
#   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 &lt;File()&gt;, &lt;Type()&gt;, or &lt;Prototype()&gt;; those will be defined in child elements.  Similarly, the child elements will
#   not define &lt;Symbol()&gt; 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:
#   &gt; [Element]
#   &gt; - Symbol
#   &gt; - Package
#   &gt; - File
#   &gt; - Type
#   &gt; - Prototype
#   &gt; - Summary
#
#   A symbol that is defined by multiple packages, each with only one file:
#   &gt; [Element]
#   &gt; - Symbol
#   &gt; - Package
#   &gt;     [Element]
#   &gt;     - Package
#   &gt;     - File
#   &gt;     - Type
#   &gt;     - Prototype
#   &gt;     - Summary
#   &gt;     [Element]
#   &gt;     - ...
#
#   A symbol that is defined by one package, but has multiple files
#   &gt; [Element]
#   &gt; - Symbol
#   &gt; - Package
#   &gt; - File
#   &gt;    [Element]
#   &gt;    - File
#   &gt;    - Type
#   &gt;    - Protype
#   &gt;    - Summary
#   &gt;    [Element]
#   &gt;    - ...
#
#   A symbol that is defined by multiple packages which have multiple files:
#   &gt; [Element]
#   &gt; - Symbol
#   &gt; - Package
#   &gt;    [Element]
#   &gt;    - Package
#   &gt;    - File
#   &gt;      [Element]
#   &gt;      - File
#   &gt;      - Type
#   &gt;      - Prototype
#   &gt;      - Summary
#   &gt;      [Element]
#   &gt;      - ...
#   &gt;    [Element]
#   &gt;    - ...
#
#   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 &lt;HasMultiplePackages()&gt; and
#   &lt;HasMultipleFiles()&gt; to determine whether you need to add a subindex for it.
#
#
#   Combining Properties:
#
#   All IndexElements also have combining properties set.
#
#   CombinedType - The general &lt;TopicType&gt; of the entry.  Conflicts combine into &lt;TOPIC_GENERAL&gt;.
#   PackageSeparator - The package separator symbol of the entry.  Conflicts combine into a dot.
#
#   So if an IndexElement only has one definition, &lt;CombinedType()&gt; is the same as the &lt;TopicType&gt; and &lt;PackageSeparator()&gt;
#   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:
#
#   &lt;SortableSymbol()&gt; 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 &lt;SymbolString&gt; without the package portion.
#   PACKAGE - The package &lt;SymbolString&gt;.  Will be a package &lt;SymbolString&gt;, undef for global, or an arrayref of
#                    &lt;NaturalDocs::SymbolTable::IndexElement&gt; objects if multiple packages define the symbol.
#   FILE - The &lt;FileName&gt; the package/symbol is defined in.  Will be the file name or an arrayref of
#            &lt;NaturalDocs::SymbolTable::IndexElements&gt; if multiple files define the package/symbol.
#   TYPE - The package/symbol/file &lt;TopicType&gt;.
#   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 &lt;TopicType&gt; 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 &lt;Merge()&gt; instead.
#
#   Parameters:
#
#       symbol  - The &lt;SymbolString&gt; without the package portion.
#       package - The package &lt;SymbolString&gt;, or undef for global.
#       file  - The symbol's definition file.
#       type  - The symbol's &lt;TopicType&gt;.
#       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 &lt;TopicType&gt;.
#       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-&gt;[COMBINED_TYPE])
        {  $object-&gt;[COMBINED_TYPE] = $object-&gt;[TYPE];  };

    if (!defined $object-&gt;[PACKAGE_SEPARATOR])
        {
        if ($object-&gt;[TYPE] eq ::TOPIC_FILE())
            {  $object-&gt;[PACKAGE_SEPARATOR] = '.';  }
        else
            {
            $object-&gt;[PACKAGE_SEPARATOR] = NaturalDocs::Languages-&gt;LanguageOf($object-&gt;[FILE])-&gt;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 &lt;SymbolString&gt;, or undef for global.
#       file  - The symbol's definition file.
#       type  - The symbol's &lt;TopicType&gt;.
#       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-&gt;HasMultiplePackages())
        {
        # If there's one package and it's the same as the new one...
        if ($package eq $self-&gt;Package())
            {
            $self-&gt;MergeFile($file, $type, $prototype, $summary);
            }

        # If there's one package and the new one is different...
        else
            {
            my $selfDefinition = NaturalDocs::SymbolTable::IndexElement-&gt;New(undef, $self-&gt;Package(), $self-&gt;File(),
                                                                                                                 $self-&gt;Type(), $self-&gt;Prototype(),
                                                                                                                 $self-&gt;Summary(), $self-&gt;CombinedType(),
                                                                                                                 $self-&gt;PackageSeparator());
            my $newDefinition = NaturalDocs::SymbolTable::IndexElement-&gt;New(undef, $package, $file, $type, $prototype,
                                                                                                                  $summary);

            $self-&gt;[PACKAGE] = [ $selfDefinition, $newDefinition ];
            $self-&gt;[FILE] = undef;
            $self-&gt;[TYPE] = undef;
            $self-&gt;[PROTOTYPE] = undef;
            $self-&gt;[SUMMARY] = undef;

            if ($newDefinition-&gt;Type() ne $self-&gt;CombinedType())
                {  $self-&gt;[COMBINED_TYPE] = ::TOPIC_GENERAL();  };
            if ($newDefinition-&gt;PackageSeparator() ne $self-&gt;PackageSeparator())
                {  $self-&gt;[PACKAGE_SEPARATOR] = '.';  };
            };
        }

    # If there's more than one package...
    else
        {
        # See if the new package is one of them.
        my $selfPackages = $self-&gt;Package();
        my $matchingPackage;

        foreach my $testPackage (@$selfPackages)
            {
            if ($package eq $testPackage-&gt;Package())
                {
                $testPackage-&gt;MergeFile($file, $type, $prototype, $summary);;
                return;
                };
            };

        my $newDefinition = NaturalDocs::SymbolTable::IndexElement-&gt;New(undef, $package, $file, $type, $prototype,
                                                                                                              $summary);
        push @{$self-&gt;[PACKAGE]}, $newDefinition;

        if ($newDefinition-&gt;Type() ne $self-&gt;CombinedType())
            {  $self-&gt;[COMBINED_TYPE] = ::TOPIC_GENERAL();  };
        if ($newDefinition-&gt;PackageSeparator() ne $self-&gt;PackageSeparator())
            {  $self-&gt;[PACKAGE_SEPARATOR] = '.';  };
        };
    };


#
#   Function: Sort
#
#   Sorts the package and file lists of the symbol.
#
sub Sort
    {
    my $self = shift;

    if ($self-&gt;HasMultipleFiles())
        {
        @{$self-&gt;[FILE]} = sort { ::StringCompare($a-&gt;File(), $b-&gt;File()) } @{$self-&gt;File()};
        }

    elsif ($self-&gt;HasMultiplePackages())
        {
        @{$self-&gt;[PACKAGE]} = sort { ::StringCompare( $a-&gt;Package(), $b-&gt;Package()) } @{$self-&gt;[PACKAGE]};

        foreach my $packageElement ( @{$self-&gt;[PACKAGE]} )
            {
            if ($packageElement-&gt;HasMultipleFiles())
                {  $packageElement-&gt;Sort();  };
            };
        };
    };


#
#   Function: MakeSortableSymbol
#
#   Generates &lt;SortableSymbol()&gt; and &lt;IgnoredPrefix()&gt;.  Should only be called after everything is merged.
#
sub MakeSortableSymbol
    {
    my $self = shift;

    my $finalLanguage;

    if ($self-&gt;HasMultiplePackages() || $self-&gt;HasMultipleFiles())
        {
        # Collect all the files that define this symbol.

        my @files;

        if ($self-&gt;HasMultipleFiles())
            {
            my $fileElements = $self-&gt;File();

            foreach my $fileElement (@$fileElements)
                {  push @files, $fileElement-&gt;File();  };
            }
        else # HasMultiplePackages
            {
            my $packages = $self-&gt;Package();

            foreach my $package (@$packages)
                {
                if ($package-&gt;HasMultipleFiles())
                    {
                    my $fileElements = $package-&gt;File();

                    foreach my $fileElement (@$fileElements)
                        {  push @files, $fileElement-&gt;File();  };
                    }
                else
                    {  push @files, $package-&gt;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-&gt;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 &gt; $topCount)
                {
                $topCount = $count;
                @topLanguages = ( $language );
                }
            elsif ($count == $topCount)
                {
                push @topLanguages, $language;
                };
            };

        if (scalar @topLanguages == 1)
            {  $finalLanguage = $topLanguages[0];  }
        else
            {
            if ($topLanguages[0]-&gt;Name() ne 'Text File')
                {  $finalLanguage = $topLanguages[0];  }
            else
                {  $finalLanguage = $topLanguages[1];  };
            };
        }

    else # !hasMultiplePackages &amp;&amp; !hasMultipleFiles
        {  $finalLanguage = NaturalDocs::Languages-&gt;LanguageOf($self-&gt;File());  };

    my $textSymbol = NaturalDocs::SymbolString-&gt;ToText($self-&gt;Symbol(), $self-&gt;PackageSeparator());
    my $ignoredPrefixLength = $finalLanguage-&gt;IgnoredPrefixLength($textSymbol, $self-&gt;CombinedType());

    if ($ignoredPrefixLength)
        {
        $self-&gt;[IGNORED_PREFIX] = substr($textSymbol, 0, $ignoredPrefixLength);
        $self-&gt;[SORTABLE_SYMBOL] = substr($textSymbol, $ignoredPrefixLength);
        }
    else
        {  $self-&gt;[SORTABLE_SYMBOL] = $textSymbol;  };
    };



###############################################################################
#
#   Functions: Information Functions
#
#   Symbol - Returns the &lt;SymbolString&gt; without the package portion.
#   Package - If &lt;HasMultiplePackages()&gt; is true, returns an arrayref of &lt;NaturalDocs::SymbolTable::IndexElement&gt; objects.
#                  Otherwise returns the package &lt;SymbolString&gt;, or undef if global.
#   File - If &lt;HasMultipleFiles()&gt; is true, returns an arrayref of &lt;NaturalDocs::SymbolTable::IndexElement&gt; objects.  Otherwise
#           returns the name of the definition file.
#   Type - Returns the &lt;TopicType&gt; 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 &lt;TopicType&gt; 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 &lt;MakeSortableSymbol()&gt;.
#   IgnoredPrefix - Returns the part of the symbol that was stripped off to make the &lt;SortableSymbol()&gt;, or undef if none.
#                          Only available after calling &lt;MakeSortableSymbol()&gt;.
#

#   Function: HasMultiplePackages
#   Returns whether &lt;Packages()&gt; is broken out into more elements.
sub HasMultiplePackages
    {  return ref($_[0]-&gt;[PACKAGE]);  };

#   Function: HasMultipleFiles
#   Returns whether &lt;File()&gt; is broken out into more elements.
sub HasMultipleFiles
    {  return ref($_[0]-&gt;[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 &lt;TopicType&gt;.
#       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-&gt;HasMultipleFiles())
        {
        # If there's one file and it's the different from the new one...
        if ($file ne $self-&gt;File())
            {
            my $selfDefinition = NaturalDocs::SymbolTable::IndexElement-&gt;New(undef, undef, $self-&gt;File(), $self-&gt;Type(),
                                                                                                                 $self-&gt;Prototype(), $self-&gt;Summary(),
                                                                                                                 $self-&gt;CombinedType(),
                                                                                                                 $self-&gt;PackageSeparator());
            my $newDefinition = NaturalDocs::SymbolTable::IndexElement-&gt;New(undef, undef, $file, $type, $prototype,
                                                                                                                  $summary);

            $self-&gt;[FILE] = [ $selfDefinition, $newDefinition ];
            $self-&gt;[TYPE] = undef;
            $self-&gt;[PROTOTYPE] = undef;
            $self-&gt;[SUMMARY] = undef;

            if ($newDefinition-&gt;Type() ne $self-&gt;CombinedType())
                {  $self-&gt;[COMBINED_TYPE] = ::TOPIC_GENERAL();  };
            if ($newDefinition-&gt;PackageSeparator() ne $self-&gt;PackageSeparator())
                {  $self-&gt;[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-&gt;File();

        foreach my $testElement (@$files)
            {
            if ($testElement-&gt;File() eq $file)
                {
                # If the new file's already in the index, ignore the duplicate.
                return;
                };
            };

        my $newDefinition = NaturalDocs::SymbolTable::IndexElement-&gt;New(undef, undef, $file, $type, $prototype,
                                                                                                              $summary);
        push @{$self-&gt;[FILE]}, $newDefinition;

        if ($newDefinition-&gt;Type() ne $self-&gt;CombinedType())
            {  $self-&gt;[COMBINED_TYPE] = ::TOPIC_GENERAL();  };
        if ($newDefinition-&gt;PackageSeparator() ne $self-&gt;PackageSeparator())
            {  $self-&gt;[PACKAGE_SEPARATOR] = '.';  };
        };
    };


1;
</pre></body></html>