diff options
Diffstat (limited to 'docs/tool/Modules/NaturalDocs/Parser')
| -rw-r--r-- | docs/tool/Modules/NaturalDocs/Parser/JavaDoc.pm | 464 | ||||
| -rw-r--r-- | docs/tool/Modules/NaturalDocs/Parser/Native.pm | 1060 | ||||
| -rw-r--r-- | docs/tool/Modules/NaturalDocs/Parser/ParsedTopic.pm | 253 |
3 files changed, 1777 insertions, 0 deletions
diff --git a/docs/tool/Modules/NaturalDocs/Parser/JavaDoc.pm b/docs/tool/Modules/NaturalDocs/Parser/JavaDoc.pm new file mode 100644 index 00000000..860b0c5f --- /dev/null +++ b/docs/tool/Modules/NaturalDocs/Parser/JavaDoc.pm @@ -0,0 +1,464 @@ +############################################################################### +# +# Package: NaturalDocs::Parser::JavaDoc +# +############################################################################### +# +# A package for translating JavaDoc topics into Natural Docs. +# +# Supported tags: +# +# - @param +# - @author +# - @deprecated +# - @code, @literal (doesn't change font) +# - @exception, @throws (doesn't link to class) +# - @link, @linkplain (doesn't change font) +# - @return, @returns +# - @see +# - @since +# - @value (shown as link instead of replacement) +# - @version +# +# Stripped tags: +# +# - @inheritDoc +# - @serial, @serialField, @serialData +# - All other block level tags. +# +# Unsupported tags: +# +# These will appear literally in the output because I cannot handle them easily. +# +# - @docRoot +# - Any other tags not mentioned +# +# Supported HTML: +# +# - p +# - b, i, u +# - pre +# - a href +# - ol, ul, li (ol gets converted to ul) +# - gt, lt, amp, quot, nbsp entities +# +# Stripped HTML: +# +# - code +# - HTML comments +# +# Unsupported HTML: +# +# These will appear literally in the output because I cannot handle them easily. +# +# - Any tags with additional properties other than a href. (ex. <p class=Something>) +# - Any other tags not mentioned +# +# Reference: +# +# http://java.sun.com/j2se/1.5.0/docs/tooldocs/windows/javadoc.html +# +############################################################################### + +# This file is part of Natural Docs, which is Copyright (C) 2003-2008 Greg Valure +# Natural Docs is licensed under the GPL + +use strict; +use integer; + +package NaturalDocs::Parser::JavaDoc; + + +# +# hash: blockTags +# An existence hash of the all-lowercase JavaDoc block tags, not including the @. +# +my %blockTags = ( 'param' => 1, 'author' => 1, 'deprecated' => 1, 'exception' => 1, 'return' => 1, 'see' => 1, + 'serial' => 1, 'serialfield' => 1, 'serialdata' => 1, 'since' => 1, 'throws' => 1, 'version' => 1, + 'returns' => 1 ); + +# +# hash: inlineTags +# An existence hash of the all-lowercase JavaDoc inline tags, not including the @. +# +my %inlineTags = ( 'inheritdoc' => 1, 'docroot' => 1, 'code' => 1, 'literal' => 1, 'link' => 1, 'linkplain' => 1, 'value' => 1 ); + + +## +# Examines the comment and returns whether it is *definitely* JavaDoc content, i.e. is owned by this package. +# +# Parameters: +# +# commentLines - An arrayref of the comment lines. Must have been run through <NaturalDocs::Parser->CleanComment()>. +# isJavaDoc - Whether the comment is JavaDoc styled. This doesn't necessarily mean it has JavaDoc content. +# +# Returns: +# +# Whether the comment is *definitely* JavaDoc content. +# +sub IsMine #(string[] commentLines, bool isJavaDoc) + { + my ($self, $commentLines, $isJavaDoc) = @_; + + if (!$isJavaDoc) + { return undef; }; + + for (my $line = 0; $line < scalar @$commentLines; $line++) + { + if ($commentLines->[$line] =~ /^ *@([a-z]+) /i && exists $blockTags{$1} || + $commentLines->[$line] =~ /\{@([a-z]+) /i && exists $inlineTags{$1}) + { + return 1; + }; + }; + + return 0; + }; + + +## +# Parses the JavaDoc-syntax comment and adds it to the parsed topic list. +# +# Parameters: +# +# commentLines - An arrayref of the comment lines. Must have been run through <NaturalDocs::Parser->CleanComment()>. +# *The original memory will be changed.* +# isJavaDoc - Whether the comment is JavaDoc styled. This doesn't necessarily mean it has JavaDoc content. +# lineNumber - The line number of the first of the comment lines. +# parsedTopics - A reference to the array where any new <NaturalDocs::Parser::ParsedTopics> should be placed. +# +# Returns: +# +# The number of parsed topics added to the array, which in this case will always be one. +# +sub ParseComment #(string[] commentLines, bool isJavaDoc, int lineNumber, ParsedTopics[]* parsedTopics) + { + my ($self, $commentLines, $isJavaDoc, $lineNumber, $parsedTopics) = @_; + + + # Stage one: Before block level tags. + + my $i = 0; + my $output; + my $unformattedText; + my $inCode; + my $sharedCodeIndent; + + while ($i < scalar @$commentLines && + !($commentLines->[$i] =~ /^ *@([a-z]+) /i && exists $blockTags{$1}) ) + { + my $line = $self->ConvertAmpChars($commentLines->[$i]); + my @tokens = split(/(<\/?pre>)/, $line); + + foreach my $token (@tokens) + { + if ($token =~ /^<pre>$/i) + { + if (!$inCode && $unformattedText) + { + $output .= '<p>' . $self->FormatText($unformattedText, 1) . '</p>'; + }; + + $inCode = 1; + $unformattedText = undef; + } + elsif ($token =~ /^<\/pre>$/i) + { + if ($inCode && $unformattedText) + { + $unformattedText =~ s/^ {$sharedCodeIndent}//mg; + $unformattedText =~ s/\n{3,}/\n\n/g; + $unformattedText =~ s/\n+$//; + $output .= '<code>' . $unformattedText . '</code>'; + + $sharedCodeIndent = undef; + }; + + $inCode = 0; + $unformattedText = undef; + } + elsif (length($token)) + { + if (!$inCode) + { + $token =~ s/^ +//; + if ($unformattedText) + { $unformattedText .= ' '; }; + } + else + { + $token =~ /^( *)/; + my $indent = length($1); + + if (!defined $sharedCodeIndent || $indent < $sharedCodeIndent) + { $sharedCodeIndent = $indent; }; + }; + + $unformattedText .= $token; + }; + }; + + if ($inCode && $unformattedText) + { $unformattedText .= "\n"; }; + + $i++; + }; + + if ($unformattedText) + { + if ($inCode) + { + $unformattedText =~ s/^ {$sharedCodeIndent}//mg; + $unformattedText =~ s/\n{3,}/\n\n/g; + $unformattedText =~ s/\n+$//; + $output .= '<code>' . $unformattedText . '</code>'; + } + else + { $output .= '<p>' . $self->FormatText($unformattedText, 1) . '</p>'; }; + + $unformattedText = undef; + }; + + + # Stage two: Block level tags. + + my ($keyword, $value, $unformattedTextPtr, $unformattedTextCloser); + my ($params, $authors, $deprecation, $throws, $returns, $seeAlso, $since, $version); + + + while ($i < scalar @$commentLines) + { + my $line = $self->ConvertAmpChars($commentLines->[$i]); + $line =~ s/^ +//; + + if ($line =~ /^@([a-z]+) ?(.*)$/i) + { + ($keyword, $value) = (lc($1), $2); + + # Process the previous one, if any. + if ($unformattedText) + { + $$unformattedTextPtr .= $self->FormatText($unformattedText) . $unformattedTextCloser; + $unformattedText = undef; + }; + + if ($keyword eq 'param') + { + $value =~ /^([a-z0-9_]+) *(.*)$/i; + + $params .= '<de>' . $1 . '</de><dd>'; + $unformattedText = $2; + + $unformattedTextPtr = \$params; + $unformattedTextCloser = '</dd>'; + } + elsif ($keyword eq 'exception' || $keyword eq 'throws') + { + $value =~ /^([a-z0-9_]+) *(.*)$/i; + + $throws .= '<de>' . $1 . '</de><dd>'; + $unformattedText = $2; + + $unformattedTextPtr = \$throws; + $unformattedTextCloser = '</dd>'; + } + elsif ($keyword eq 'return' || $keyword eq 'returns') + { + if ($returns) + { $returns .= ' '; }; + + $unformattedText = $value; + $unformattedTextPtr = \$returns; + $unformattedTextCloser = undef; + } + elsif ($keyword eq 'author') + { + if ($authors) + { $authors .= ', '; }; + + $unformattedText = $value; + $unformattedTextPtr = \$authors; + $unformattedTextCloser = undef; + } + elsif ($keyword eq 'deprecated') + { + if ($deprecation) + { $deprecation .= ' '; }; + + $unformattedText = $value; + $unformattedTextPtr = \$deprecation; + $unformattedTextCloser = undef; + } + elsif ($keyword eq 'since') + { + if ($since) + { $since .= ', '; }; + + $unformattedText = $value; + $unformattedTextPtr = \$since; + $unformattedTextCloser = undef; + } + elsif ($keyword eq 'version') + { + if ($version) + { $version .= ', '; }; + + $unformattedText = $value; + $unformattedTextPtr = \$version; + $unformattedTextCloser = undef; + } + elsif ($keyword eq 'see') + { + if ($seeAlso) + { $seeAlso .= ', '; }; + + $unformattedText = undef; + + if ($value =~ /^&(?:quot|lt);/i) + { $seeAlso .= $self->FormatText($value); } + else + { $seeAlso .= $self->ConvertLink($value); }; + }; + + # Everything else will be skipped. + } + elsif ($unformattedText) + { + $unformattedText .= ' ' . $line; + }; + + $i++; + }; + + if ($unformattedText) + { + $$unformattedTextPtr .= $self->FormatText($unformattedText) . $unformattedTextCloser; + $unformattedText = undef; + }; + + if ($params) + { $output .= '<h>Parameters</h><dl>' . $params . '</dl>'; }; + if ($returns) + { $output .= '<h>Returns</h><p>' . $returns . '</p>'; }; + if ($throws) + { $output .= '<h>Throws</h><dl>' . $throws . '</dl>'; }; + if ($since) + { $output .= '<h>Since</h><p>' . $since . '</p>'; }; + if ($version) + { $output .= '<h>Version</h><p>' . $version . '</p>'; }; + if ($deprecation) + { $output .= '<h>Deprecated</h><p>' . $deprecation . '</p>'; }; + if ($authors) + { $output .= '<h>Author</h><p>' . $authors . '</p>'; }; + if ($seeAlso) + { $output .= '<h>See Also</h><p>' . $seeAlso . '</p>'; }; + + + # Stage three: Build the parsed topic. + + my $summary = NaturalDocs::Parser->GetSummaryFromBody($output); + + push @$parsedTopics, NaturalDocs::Parser::ParsedTopic->New(undef, undef, undef, undef, undef, $summary, + $output, $lineNumber, undef); + return 1; + }; + + +## +# Translates any inline tags or HTML codes to <NDMarkup> and returns it. +# +sub FormatText #(string text, bool inParagraph) + { + my ($self, $text, $inParagraph) = @_; + + # JavaDoc Literal + + $text =~ s/\{\@(?:code|literal) ([^\}]*)\}/$self->ConvertAmpChars($1)/gie; + + + # HTML + + $text =~ s/<b>(.*?)<\/b>/<b>$1<\/b>/gi; + $text =~ s/<i>(.*?)<\/i>/<i>$1<\/i>/gi; + $text =~ s/<u>(.*?)<\/u>/<u>$1<\/u>/gi; + + $text =~ s/<code>(.*?)<\/code>/$1/gi; + + $text =~ s/<ul.*?>(.*?)<\/ul>/<ul>$1<\/ul>/gi; + $text =~ s/<ol.*?>(.*?)<\/ol>/<ul>$1<\/ul>/gi; + $text =~ s/<li.*?>(.*?)<\/li>/<li>$1<\/li>/gi; + + $text =~ s/<!--.*?-->//gi; + + $text =~ s/<\/p>//gi; + $text =~ s/^<p>//i; + if ($inParagraph) + { $text =~ s/<p>/<\/p><p>/gi; } + else + { $text =~ s/<p>//gi; }; + + $text =~ s/<a href="mailto:(.*?)".*?>(.*?)<\/a>/$self->MakeEMailLink($1, $2)/gie; + $text =~ s/<a href="(.*?)".*?>(.*?)<\/a>/$self->MakeURLLink($1, $2)/gie; + + $text =~ s/&nbsp;/ /gi; + $text =~ s/&amp;/&/gi; + $text =~ s/&gt;/>/gi; + $text =~ s/&lt;/</gi; + $text =~ s/&quot;/"/gi; + + + + # JavaDoc + + $text =~ s/\{\@inheritdoc\}//gi; + $text =~ s/\{\@(?:linkplain|link|value) ([^\}]*)\}/$self->ConvertLink($1)/gie; + + return $text; + }; + + +sub ConvertAmpChars #(text) + { + my ($self, $text) = @_; + + $text =~ s/&/&/g; + $text =~ s/</</g; + $text =~ s/>/>/g; + $text =~ s/"/"/g; + + return $text; + }; + +sub ConvertLink #(text) + { + my ($self, $text) = @_; + + $text =~ /^ *([a-z0-9\_\.\:\#]+(?:\([^\)]*\))?) *(.*)$/i; + my ($target, $label) = ($1, $2); + + # Convert the anchor to part of the link, but remove it altogether if it's the beginning of the link. + $target =~ s/^\#//; + $target =~ s/\#/\./; + + $label =~ s/ +$//; + + if (!length $label) + { return '<link target="' . $target . '" name="' . $target . '" original="' . $target . '">'; } + else + { return '<link target="' . $target . '" name="' . $label . '" original="' . $label . ' (' . $target . ')">'; }; + }; + +sub MakeURLLink #(target, text) + { + my ($self, $target, $text) = @_; + return '<url target="' . $target . '" name="' . $text . '">'; + }; + +sub MakeEMailLink #(target, text) + { + my ($self, $target, $text) = @_; + return '<email target="' . $target . '" name="' . $text . '">'; + }; + + +1; diff --git a/docs/tool/Modules/NaturalDocs/Parser/Native.pm b/docs/tool/Modules/NaturalDocs/Parser/Native.pm new file mode 100644 index 00000000..61ba97e5 --- /dev/null +++ b/docs/tool/Modules/NaturalDocs/Parser/Native.pm @@ -0,0 +1,1060 @@ +############################################################################### +# +# Package: NaturalDocs::Parser::Native +# +############################################################################### +# +# A package that converts comments from Natural Docs' native format into <NaturalDocs::Parser::ParsedTopic> objects. +# Unlike most second-level packages, these are packages and not object classes. +# +############################################################################### + +# This file is part of Natural Docs, which is Copyright (C) 2003-2008 Greg Valure +# Natural Docs is licensed under the GPL + + +use strict; +use integer; + +package NaturalDocs::Parser::Native; + + +############################################################################### +# Group: Variables + + +# Return values of TagType(). Not documented here. +use constant POSSIBLE_OPENING_TAG => 1; +use constant POSSIBLE_CLOSING_TAG => 2; +use constant NOT_A_TAG => 3; + + +# +# var: package +# +# A <SymbolString> representing the package normal topics will be a part of at the current point in the file. This is a package variable +# because it needs to be reserved between function calls. +# +my $package; + +# +# hash: functionListIgnoredHeadings +# +# An existence hash of all the headings that prevent the parser from creating function list symbols. Whenever one of +# these headings are used in a function list topic, symbols are not created from definition lists until the next heading. The keys +# are in all lowercase. +# +my %functionListIgnoredHeadings = ( 'parameters' => 1, + 'parameter' => 1, + 'params' => 1, + 'param' => 1, + 'arguments' => 1, + 'argument' => 1, + 'args' => 1, + 'arg' => 1 ); + + +############################################################################### +# Group: Interface Functions + + +# +# Function: Start +# +# This will be called whenever a file is about to be parsed. It allows the package to reset its internal state. +# +sub Start + { + my ($self) = @_; + $package = undef; + }; + + +# +# Function: IsMine +# +# Examines the comment and returns whether it is *definitely* Natural Docs content, i.e. it is owned by this package. Note +# that a comment can fail this function and still be interpreted as a Natural Docs content, for example a JavaDoc-styled comment +# that doesn't have header lines but no JavaDoc tags either. +# +# Parameters: +# +# commentLines - An arrayref of the comment lines. Must have been run through <NaturalDocs::Parser->CleanComment()>. +# isJavaDoc - Whether the comment was JavaDoc-styled. +# +# Returns: +# +# Whether the comment is *definitely* Natural Docs content. +# +sub IsMine #(string[] commentLines, bool isJavaDoc) + { + my ($self, $commentLines, $isJavaDoc) = @_; + + # Skip to the first line with content. + my $line = 0; + + while ($line < scalar @$commentLines && !length $commentLines->[$line]) + { $line++; }; + + return $self->ParseHeaderLine($commentLines->[$line]); + }; + + + +# +# Function: ParseComment +# +# This will be called whenever a comment capable of containing Natural Docs content is found. +# +# Parameters: +# +# commentLines - An arrayref of the comment lines. Must have been run through <NaturalDocs::Parser->CleanComment()>. +# *The original memory will be changed.* +# isJavaDoc - Whether the comment is JavaDoc styled. +# lineNumber - The line number of the first of the comment lines. +# parsedTopics - A reference to the array where any new <NaturalDocs::Parser::ParsedTopics> should be placed. +# +# Returns: +# +# The number of parsed topics added to the array, or zero if none. +# +sub ParseComment #(commentLines, isJavaDoc, lineNumber, parsedTopics) + { + my ($self, $commentLines, $isJavaDoc, $lineNumber, $parsedTopics) = @_; + + my $topicCount = 0; + my $prevLineBlank = 1; + my $inCodeSection = 0; + + my ($type, $scope, $isPlural, $title, $symbol); + #my $package; # package variable. + my ($newKeyword, $newTitle); + + my $index = 0; + + my $bodyStart = 0; + my $bodyEnd = 0; # Not inclusive. + + while ($index < scalar @$commentLines) + { + # Everything but leading whitespace was removed beforehand. + + # If we're in a code section... + if ($inCodeSection) + { + if ($commentLines->[$index] =~ /^ *\( *(?:end|finish|done)(?: +(?:table|code|example|diagram))? *\)$/i) + { $inCodeSection = undef; }; + + $prevLineBlank = 0; + $bodyEnd++; + } + + # If the line is empty... + elsif (!length($commentLines->[$index])) + { + $prevLineBlank = 1; + + if ($topicCount) + { $bodyEnd++; }; + } + + # If the line has a recognized header and the previous line is blank... + elsif ($prevLineBlank && (($newKeyword, $newTitle) = $self->ParseHeaderLine($commentLines->[$index])) ) + { + # Process the previous one, if any. + + if ($topicCount) + { + if ($scope == ::SCOPE_START() || $scope == ::SCOPE_END()) + { $package = undef; }; + + my $body = $self->FormatBody($commentLines, $bodyStart, $bodyEnd, $type, $isPlural); + my $newTopic = $self->MakeParsedTopic($type, $title, $package, $body, $lineNumber + $bodyStart - 1, $isPlural); + push @$parsedTopics, $newTopic; + + $package = $newTopic->Package(); + }; + + $title = $newTitle; + + my $typeInfo; + ($type, $typeInfo, $isPlural) = NaturalDocs::Topics->KeywordInfo($newKeyword); + $scope = $typeInfo->Scope(); + + $bodyStart = $index + 1; + $bodyEnd = $index + 1; + + $topicCount++; + + $prevLineBlank = 0; + } + + # If we're on a non-empty, non-header line of a JavaDoc-styled comment and we haven't started a topic yet... + elsif ($isJavaDoc && !$topicCount) + { + $type = undef; + $scope = ::SCOPE_NORMAL(); # The scope repair and topic merging processes will handle if this is a class topic. + $isPlural = undef; + $title = undef; + $symbol = undef; + + $bodyStart = $index; + $bodyEnd = $index + 1; + + $topicCount++; + + $prevLineBlank = undef; + } + + # If we're on a normal content line within a topic + elsif ($topicCount) + { + $prevLineBlank = 0; + $bodyEnd++; + + if ($commentLines->[$index] =~ /^ *\( *(?:(?:start|begin)? +)?(?:table|code|example|diagram) *\)$/i) + { $inCodeSection = 1; }; + }; + + + $index++; + }; + + + # Last one, if any. This is the only one that gets the prototypes. + if ($bodyStart) + { + if ($scope == ::SCOPE_START() || $scope == ::SCOPE_END()) + { $package = undef; }; + + my $body = $self->FormatBody($commentLines, $bodyStart, $bodyEnd, $type, $isPlural); + my $newTopic = $self->MakeParsedTopic($type, $title, $package, $body, $lineNumber + $bodyStart - 1, $isPlural); + push @$parsedTopics, $newTopic; + $topicCount++; + + $package = $newTopic->Package(); + }; + + return $topicCount; + }; + + +# +# Function: ParseHeaderLine +# +# If the passed line is a topic header, returns the array ( keyword, title ). Otherwise returns an empty array. +# +sub ParseHeaderLine #(line) + { + my ($self, $line) = @_; + + if ($line =~ /^ *([a-z0-9 ]*[a-z0-9]): +(.*)$/i) + { + my ($keyword, $title) = ($1, $2); + + # We need to do it this way because if you do "if (ND:T->KeywordInfo($keyword)" and the last element of the array it + # returns is false, the statement is false. That is really retarded, but there it is. + my ($type, undef, undef) = NaturalDocs::Topics->KeywordInfo($keyword); + + if ($type) + { return ($keyword, $title); } + else + { return ( ); }; + } + else + { return ( ); }; + }; + + + +############################################################################### +# Group: Support Functions + + +# +# Function: MakeParsedTopic +# +# Creates a <NaturalDocs::Parser::ParsedTopic> object for the passed parameters. Scope is gotten from +# the package variable <package> instead of from the parameters. The summary is generated from the body. +# +# Parameters: +# +# type - The <TopicType>. May be undef for headerless topics. +# title - The title of the topic. May be undef for headerless topics. +# package - The package <SymbolString> the topic appears in. +# body - The topic's body in <NDMarkup>. +# lineNumber - The topic's line number. +# isList - Whether the topic is a list. +# +# Returns: +# +# The <NaturalDocs::Parser::ParsedTopic> object. +# +sub MakeParsedTopic #(type, title, package, body, lineNumber, isList) + { + my ($self, $type, $title, $package, $body, $lineNumber, $isList) = @_; + + my $summary; + + if (defined $body) + { $summary = NaturalDocs::Parser->GetSummaryFromBody($body); }; + + return NaturalDocs::Parser::ParsedTopic->New($type, $title, $package, undef, undef, $summary, + $body, $lineNumber, $isList); + }; + + +# +# Function: FormatBody +# +# Converts the section body to <NDMarkup>. +# +# Parameters: +# +# commentLines - The arrayref of comment lines. +# startingIndex - The starting index of the body to format. +# endingIndex - The ending index of the body to format, *not* inclusive. +# type - The type of the section. May be undef for headerless comments. +# isList - Whether it's a list topic. +# +# Returns: +# +# The body formatted in <NDMarkup>. +# +sub FormatBody #(commentLines, startingIndex, endingIndex, type, isList) + { + my ($self, $commentLines, $startingIndex, $endingIndex, $type, $isList) = @_; + + use constant TAG_NONE => 1; + use constant TAG_PARAGRAPH => 2; + use constant TAG_BULLETLIST => 3; + use constant TAG_DESCRIPTIONLIST => 4; + use constant TAG_HEADING => 5; + use constant TAG_PREFIXCODE => 6; + use constant TAG_TAGCODE => 7; + + my %tagEnders = ( TAG_NONE() => '', + TAG_PARAGRAPH() => '</p>', + TAG_BULLETLIST() => '</li></ul>', + TAG_DESCRIPTIONLIST() => '</dd></dl>', + TAG_HEADING() => '</h>', + TAG_PREFIXCODE() => '</code>', + TAG_TAGCODE() => '</code>' ); + + my $topLevelTag = TAG_NONE; + + my $output; + my $textBlock; + my $prevLineBlank = 1; + + my $codeBlock; + my $removedCodeSpaces; + + my $ignoreListSymbols; + + my $index = $startingIndex; + + while ($index < $endingIndex) + { + # If we're in a tagged code section... + if ($topLevelTag == TAG_TAGCODE) + { + if ($commentLines->[$index] =~ /^ *\( *(?:end|finish|done)(?: +(?:table|code|example|diagram))? *\)$/i) + { + $codeBlock =~ s/\n+$//; + $output .= NaturalDocs::NDMarkup->ConvertAmpChars($codeBlock) . '</code>'; + $codeBlock = undef; + $topLevelTag = TAG_NONE; + $prevLineBlank = undef; + } + else + { + $self->AddToCodeBlock($commentLines->[$index], \$codeBlock, \$removedCodeSpaces); + }; + } + + # If the line starts with a code designator... + elsif ($commentLines->[$index] =~ /^ *[>:|](.*)$/) + { + my $code = $1; + + if ($topLevelTag == TAG_PREFIXCODE) + { + $self->AddToCodeBlock($code, \$codeBlock, \$removedCodeSpaces); + } + else # $topLevelTag != TAG_PREFIXCODE + { + if (defined $textBlock) + { + $output .= $self->RichFormatTextBlock($textBlock) . $tagEnders{$topLevelTag}; + $textBlock = undef; + }; + + $topLevelTag = TAG_PREFIXCODE; + $output .= '<code>'; + $self->AddToCodeBlock($code, \$codeBlock, \$removedCodeSpaces); + }; + } + + # If we're not in either code style... + else + { + # Strip any leading whitespace. + $commentLines->[$index] =~ s/^ +//; + + # If we were in a prefixed code section... + if ($topLevelTag == TAG_PREFIXCODE) + { + $codeBlock =~ s/\n+$//; + $output .= NaturalDocs::NDMarkup->ConvertAmpChars($codeBlock) . '</code>'; + $codeBlock = undef; + $topLevelTag = TAG_NONE; + $prevLineBlank = undef; + }; + + + # If the line is blank... + if (!length($commentLines->[$index])) + { + # End a paragraph. Everything else ignores it for now. + if ($topLevelTag == TAG_PARAGRAPH) + { + $output .= $self->RichFormatTextBlock($textBlock) . '</p>'; + $textBlock = undef; + $topLevelTag = TAG_NONE; + }; + + $prevLineBlank = 1; + } + + # If the line starts with a bullet... + elsif ($commentLines->[$index] =~ /^[-\*o+] +([^ ].*)$/ && + substr($1, 0, 2) ne '- ') # Make sure "o - Something" is a definition, not a bullet. + { + my $bulletedText = $1; + + if (defined $textBlock) + { $output .= $self->RichFormatTextBlock($textBlock); }; + + if ($topLevelTag == TAG_BULLETLIST) + { + $output .= '</li><li>'; + } + else #($topLevelTag != TAG_BULLETLIST) + { + $output .= $tagEnders{$topLevelTag} . '<ul><li>'; + $topLevelTag = TAG_BULLETLIST; + }; + + $textBlock = $bulletedText; + + $prevLineBlank = undef; + } + + # If the line looks like a description list entry... + elsif ($commentLines->[$index] =~ /^(.+?) +- +([^ ].*)$/ && $topLevelTag != TAG_PARAGRAPH) + { + my $entry = $1; + my $description = $2; + + if (defined $textBlock) + { $output .= $self->RichFormatTextBlock($textBlock); }; + + if ($topLevelTag == TAG_DESCRIPTIONLIST) + { + $output .= '</dd>'; + } + else #($topLevelTag != TAG_DESCRIPTIONLIST) + { + $output .= $tagEnders{$topLevelTag} . '<dl>'; + $topLevelTag = TAG_DESCRIPTIONLIST; + }; + + if (($isList && !$ignoreListSymbols) || $type eq ::TOPIC_ENUMERATION()) + { + $output .= '<ds>' . NaturalDocs::NDMarkup->ConvertAmpChars($entry) . '</ds><dd>'; + } + else + { + $output .= '<de>' . NaturalDocs::NDMarkup->ConvertAmpChars($entry) . '</de><dd>'; + }; + + $textBlock = $description; + + $prevLineBlank = undef; + } + + # If the line could be a header... + elsif ($prevLineBlank && $commentLines->[$index] =~ /^(.*)([^ ]):$/) + { + my $headerText = $1 . $2; + + if (defined $textBlock) + { + $output .= $self->RichFormatTextBlock($textBlock); + $textBlock = undef; + } + + $output .= $tagEnders{$topLevelTag}; + $topLevelTag = TAG_NONE; + + $output .= '<h>' . $self->RichFormatTextBlock($headerText) . '</h>'; + + if ($type eq ::TOPIC_FUNCTION() && $isList) + { + $ignoreListSymbols = exists $functionListIgnoredHeadings{lc($headerText)}; + }; + + $prevLineBlank = undef; + } + + # If the line looks like a code tag... + elsif ($commentLines->[$index] =~ /^\( *(?:(?:start|begin)? +)?(?:table|code|example|diagram) *\)$/i) + { + if (defined $textBlock) + { + $output .= $self->RichFormatTextBlock($textBlock); + $textBlock = undef; + }; + + $output .= $tagEnders{$topLevelTag} . '<code>'; + $topLevelTag = TAG_TAGCODE; + } + + # If the line looks like an inline image... + elsif ($commentLines->[$index] =~ /^(\( *see +)([^\)]+?)( *\))$/i) + { + if (defined $textBlock) + { + $output .= $self->RichFormatTextBlock($textBlock); + $textBlock = undef; + }; + + $output .= $tagEnders{$topLevelTag}; + $topLevelTag = TAG_NONE; + + $output .= '<img mode="inline" target="' . NaturalDocs::NDMarkup->ConvertAmpChars($2) . '" ' + . 'original="' . NaturalDocs::NDMarkup->ConvertAmpChars($1 . $2 . $3) . '">'; + + $prevLineBlank = undef; + } + + # If the line isn't any of those, we consider it normal text. + else + { + # A blank line followed by normal text ends lists. We don't handle this when we detect if the line's blank because + # we don't want blank lines between list items to break the list. + if ($prevLineBlank && ($topLevelTag == TAG_BULLETLIST || $topLevelTag == TAG_DESCRIPTIONLIST)) + { + $output .= $self->RichFormatTextBlock($textBlock) . $tagEnders{$topLevelTag} . '<p>'; + + $topLevelTag = TAG_PARAGRAPH; + $textBlock = undef; + } + + elsif ($topLevelTag == TAG_NONE) + { + $output .= '<p>'; + $topLevelTag = TAG_PARAGRAPH; + # textBlock will already be undef. + }; + + if (defined $textBlock) + { $textBlock .= ' '; }; + + $textBlock .= $commentLines->[$index]; + + $prevLineBlank = undef; + }; + }; + + $index++; + }; + + # Clean up anything left dangling. + if (defined $textBlock) + { + $output .= $self->RichFormatTextBlock($textBlock) . $tagEnders{$topLevelTag}; + } + elsif (defined $codeBlock) + { + $codeBlock =~ s/\n+$//; + $output .= NaturalDocs::NDMarkup->ConvertAmpChars($codeBlock) . '</code>'; + }; + + return $output; + }; + + +# +# Function: AddToCodeBlock +# +# Adds a line of text to a code block, handling all the indentation processing required. +# +# Parameters: +# +# line - The line of text to add. +# codeBlockRef - A reference to the code block to add it to. +# removedSpacesRef - A reference to a variable to hold the number of spaces removed. It needs to be stored between calls. +# It will reset itself automatically when the code block codeBlockRef points to is undef. +# +sub AddToCodeBlock #(line, codeBlockRef, removedSpacesRef) + { + my ($self, $line, $codeBlockRef, $removedSpacesRef) = @_; + + $line =~ /^( *)(.*)$/; + my ($spaces, $code) = ($1, $2); + + if (!defined $$codeBlockRef) + { + if (length($code)) + { + $$codeBlockRef = $code . "\n"; + $$removedSpacesRef = length($spaces); + }; + # else ignore leading line breaks. + } + + elsif (length $code) + { + # Make sure we have the minimum amount of spaces to the left possible. + if (length($spaces) != $$removedSpacesRef) + { + my $spaceDifference = abs( length($spaces) - $$removedSpacesRef ); + my $spacesToAdd = ' ' x $spaceDifference; + + if (length($spaces) > $$removedSpacesRef) + { + $$codeBlockRef .= $spacesToAdd; + } + else + { + $$codeBlockRef =~ s/^(.)/$spacesToAdd . $1/gme; + $$removedSpacesRef = length($spaces); + }; + }; + + $$codeBlockRef .= $code . "\n"; + } + + else # (!length $code) + { + $$codeBlockRef .= "\n"; + }; + }; + + +# +# Function: RichFormatTextBlock +# +# Applies rich <NDMarkup> formatting to a chunk of text. This includes both amp chars, formatting tags, and link tags. +# +# Parameters: +# +# text - The block of text to format. +# +# Returns: +# +# The formatted text block. +# +sub RichFormatTextBlock #(text) + { + my ($self, $text) = @_; + my $output; + + + # First find bare urls, e-mail addresses, and images. We have to do this before the split because they may contain underscores + # or asterisks. We have to mark the tags with \x1E and \x1F so they don't get confused with angle brackets from the comment. + # We can't convert the amp chars beforehand because we need lookbehinds in the regexps below and they need to be + # constant length. Sucks, huh? + + $text =~ s{ + # The previous character can't be an alphanumeric or an opening angle bracket. + (?<! [a-z0-9<] ) + + # Optional mailto:. Ignored in output. + (?:mailto\:)? + + # Begin capture + ( + + # The user portion. Alphanumeric and - _. Dots can appear between, but not at the edges or more than + # one in a row. + (?: [a-z0-9\-_]+ \. )* [a-z0-9\-_]+ + + @ + + # The domain. Alphanumeric and -. Dots same as above, however, there must be at least two sections + # and the last one must be two to four alphanumeric characters (.com, .uk, .info, .203 for IP addresses) + (?: [a-z0-9\-]+ \. )+ [a-z]{2,4} + + # End capture. + ) + + # The next character can't be an alphanumeric, which should prevent .abcde from matching the two to + # four character requirement, or a closing angle bracket. + (?! [a-z0-9>] ) + + } + + {"\x1E" . 'email target="' . NaturalDocs::NDMarkup->ConvertAmpChars($1) . '" ' + . 'name="' . NaturalDocs::NDMarkup->ConvertAmpChars($1) . '"' . "\x1F"}igxe; + + $text =~ s{ + # The previous character can't be an alphanumeric or an opening angle bracket. + (?<! [a-z0-9<] ) + + # Begin capture. + ( + + # URL must start with one of the acceptable protocols. + (?:http|https|ftp|news|file)\: + + # The acceptable URL characters as far as I know. + [a-z0-9\-\=\~\@\#\%\&\_\+\/\;\:\?\*\.\,]* + + # The URL characters minus period and comma. If it ends on them, they're probably intended as + # punctuation. + [a-z0-9\-\=\~\@\#\%\&\_\+\/\;\:\?\*] + + # End capture. + ) + + # The next character must not be an acceptable character or a closing angle bracket. This will prevent the URL + # from ending early just to get a match. + (?! [a-z0-9\-\=\~\@\#\%\&\_\+\/\;\:\?\*\>] ) + + } + + {"\x1E" . 'url target="' . NaturalDocs::NDMarkup->ConvertAmpChars($1) . '" ' + . 'name="' . NaturalDocs::NDMarkup->ConvertAmpChars($1) . '"' . "\x1F"}igxe; + + + # Find image links. Inline images should already be pulled out by now. + + $text =~ s{(\( *see +)([^\)]+?)( *\))} + {"\x1E" . 'img mode="link" target="' . NaturalDocs::NDMarkup->ConvertAmpChars($2) . '" ' + . 'original="' . NaturalDocs::NDMarkup->ConvertAmpChars($1 . $2 . $3) . '"' . "\x1F"}gie; + + + + # Split the text from the potential tags. + + my @tempTextBlocks = split(/([\*_<>\x1E\x1F])/, $text); + + # Since the symbols are considered dividers, empty strings could appear between two in a row or at the beginning/end of the + # array. This could seriously screw up TagType(), so we need to get rid of them. + my @textBlocks; + + while (scalar @tempTextBlocks) + { + my $tempTextBlock = shift @tempTextBlocks; + + if (length $tempTextBlock) + { push @textBlocks, $tempTextBlock; }; + }; + + + my $bold; + my $underline; + my $underlineHasWhitespace; + + my $index = 0; + + while ($index < scalar @textBlocks) + { + if ($textBlocks[$index] eq "\x1E") + { + $output .= '<'; + $index++; + + while ($textBlocks[$index] ne "\x1F") + { + $output .= $textBlocks[$index]; + $index++; + }; + + $output .= '>'; + } + + elsif ($textBlocks[$index] eq '<' && $self->TagType(\@textBlocks, $index) == POSSIBLE_OPENING_TAG) + { + my $endingIndex = $self->ClosingTag(\@textBlocks, $index, undef); + + if ($endingIndex != -1) + { + my $linkText; + $index++; + + while ($index < $endingIndex) + { + $linkText .= $textBlocks[$index]; + $index++; + }; + # Index will be incremented again at the end of the loop. + + $linkText = NaturalDocs::NDMarkup->ConvertAmpChars($linkText); + + if ($linkText =~ /^(?:mailto\:)?((?:[a-z0-9\-_]+\.)*[a-z0-9\-_]+@(?:[a-z0-9\-]+\.)+[a-z]{2,4})$/i) + { $output .= '<email target="' . $1 . '" name="' . $1 . '">'; } + elsif ($linkText =~ /^(?:http|https|ftp|news|file)\:/i) + { $output .= '<url target="' . $linkText . '" name="' . $linkText . '">'; } + else + { $output .= '<link target="' . $linkText . '" name="' . $linkText . '" original="<' . $linkText . '>">'; }; + } + + else # it's not a link. + { + $output .= '<'; + }; + } + + elsif ($textBlocks[$index] eq '*') + { + my $tagType = $self->TagType(\@textBlocks, $index); + + if ($tagType == POSSIBLE_OPENING_TAG && $self->ClosingTag(\@textBlocks, $index, undef) != -1) + { + # ClosingTag() makes sure tags aren't opened multiple times in a row. + $bold = 1; + $output .= '<b>'; + } + elsif ($bold && $tagType == POSSIBLE_CLOSING_TAG) + { + $bold = undef; + $output .= '</b>'; + } + else + { + $output .= '*'; + }; + } + + elsif ($textBlocks[$index] eq '_') + { + my $tagType = $self->TagType(\@textBlocks, $index); + + if ($tagType == POSSIBLE_OPENING_TAG && $self->ClosingTag(\@textBlocks, $index, \$underlineHasWhitespace) != -1) + { + # ClosingTag() makes sure tags aren't opened multiple times in a row. + $underline = 1; + #underlineHasWhitespace is set by ClosingTag(). + $output .= '<u>'; + } + elsif ($underline && $tagType == POSSIBLE_CLOSING_TAG) + { + $underline = undef; + #underlineHasWhitespace will be reset by the next opening underline. + $output .= '</u>'; + } + elsif ($underline && !$underlineHasWhitespace) + { + # If there's no whitespace between underline tags, all underscores are replaced by spaces so + # _some_underlined_text_ becomes <u>some underlined text</u>. The standard _some underlined text_ + # will work too. + $output .= ' '; + } + else + { + $output .= '_'; + }; + } + + else # plain text or a > that isn't part of a link + { + $output .= NaturalDocs::NDMarkup->ConvertAmpChars($textBlocks[$index]); + }; + + $index++; + }; + + return $output; + }; + + +# +# Function: TagType +# +# Returns whether the tag is a possible opening or closing tag, or neither. "Possible" because it doesn't check if an opening tag is +# closed or a closing tag is opened, just whether the surrounding characters allow it to be a candidate for a tag. For example, in +# "A _B" the underscore is a possible opening underline tag, but in "A_B" it is not. Support function for <RichFormatTextBlock()>. +# +# Parameters: +# +# textBlocks - A reference to an array of text blocks. +# index - The index of the tag. +# +# Returns: +# +# POSSIBLE_OPENING_TAG, POSSIBLE_CLOSING_TAG, or NOT_A_TAG. +# +sub TagType #(textBlocks, index) + { + my ($self, $textBlocks, $index) = @_; + + + # Possible opening tags + + if ( ( $textBlocks->[$index] =~ /^[\*_<]$/ ) && + + # Before it must be whitespace, the beginning of the text, or ({["'-/*_. + ( $index == 0 || $textBlocks->[$index-1] =~ /[\ \t\n\(\{\[\"\'\-\/\*\_]$/ ) && + + # Notes for 2.0: Include Spanish upside down ! and ? as well as opening quotes (66) and apostrophes (6). Look into + # Unicode character classes as well. + + # After it must be non-whitespace. + ( $index + 1 < scalar @$textBlocks && $textBlocks->[$index+1] !~ /^[\ \t\n]/) && + + # Make sure we don't accept <<, <=, <-, or *= as opening tags. + ( $textBlocks->[$index] ne '<' || $textBlocks->[$index+1] !~ /^[<=-]/ ) && + ( $textBlocks->[$index] ne '*' || $textBlocks->[$index+1] !~ /^[\=\*]/ ) && + + # Make sure we don't accept * or _ before it unless it's <. + ( $textBlocks->[$index] eq '<' || $index == 0 || $textBlocks->[$index-1] !~ /[\*\_]$/) ) + { + return POSSIBLE_OPENING_TAG; + } + + + # Possible closing tags + + elsif ( ( $textBlocks->[$index] =~ /^[\*_>]$/) && + + # After it must be whitespace, the end of the text, or )}].,!?"';:-/*_. + ( $index + 1 == scalar @$textBlocks || $textBlocks->[$index+1] =~ /^[ \t\n\)\]\}\.\,\!\?\"\'\;\:\-\/\*\_]/ || + # Links also get plurals, like <link>s, <linx>es, <link>'s, and <links>'. + ( $textBlocks->[$index] eq '>' && $textBlocks->[$index+1] =~ /^(?:es|s|\')/ ) ) && + + # Notes for 2.0: Include closing quotes (99) and apostrophes (9). Look into Unicode character classes as well. + + # Before it must be non-whitespace. + ( $index != 0 && $textBlocks->[$index-1] !~ /[ \t\n]$/ ) && + + # Make sure we don't accept >>, ->, or => as closing tags. >= is already taken care of. + ( $textBlocks->[$index] ne '>' || $textBlocks->[$index-1] !~ /[>=-]$/ ) && + + # Make sure we don't accept * or _ after it unless it's >. + ( $textBlocks->[$index] eq '>' || $textBlocks->[$index+1] !~ /[\*\_]$/) ) + { + return POSSIBLE_CLOSING_TAG; + } + + else + { + return NOT_A_TAG; + }; + + }; + + +# +# Function: ClosingTag +# +# Returns whether a tag is closed or not, where it's closed if it is, and optionally whether there is any whitespace between the +# tags. Support function for <RichFormatTextBlock()>. +# +# The results of this function are in full context, meaning that if it says a tag is closed, it can be interpreted as that tag in the +# final output. It takes into account any spoiling factors, like there being two opening tags in a row. +# +# Parameters: +# +# textBlocks - A reference to an array of text blocks. +# index - The index of the opening tag. +# hasWhitespaceRef - A reference to the variable that will hold whether there is whitespace between the tags or not. If +# undef, the function will not check. If the tag is not closed, the variable will not be changed. +# +# Returns: +# +# If the tag is closed, it returns the index of the closing tag and puts whether there was whitespace between the tags in +# hasWhitespaceRef if it was specified. If the tag is not closed, it returns -1 and doesn't touch the variable pointed to by +# hasWhitespaceRef. +# +sub ClosingTag #(textBlocks, index, hasWhitespace) + { + my ($self, $textBlocks, $index, $hasWhitespaceRef) = @_; + + my $hasWhitespace; + my $closingTag; + + if ($textBlocks->[$index] eq '*' || $textBlocks->[$index] eq '_') + { $closingTag = $textBlocks->[$index]; } + elsif ($textBlocks->[$index] eq '<') + { $closingTag = '>'; } + else + { return -1; }; + + my $beginningIndex = $index; + $index++; + + while ($index < scalar @$textBlocks) + { + if ($textBlocks->[$index] eq '<' && $self->TagType($textBlocks, $index) == POSSIBLE_OPENING_TAG) + { + # If we hit a < and we're checking whether a link is closed, it's not. The first < becomes literal and the second one + # becomes the new link opening. + if ($closingTag eq '>') + { + return -1; + } + + # If we're not searching for the end of a link, we have to skip the link because formatting tags cannot appear within + # them. That's of course provided it's closed. + else + { + my $linkHasWhitespace; + + my $endIndex = $self->ClosingTag($textBlocks, $index, + ($hasWhitespaceRef && !$hasWhitespace ? \$linkHasWhitespace : undef) ); + + if ($endIndex != -1) + { + if ($linkHasWhitespace) + { $hasWhitespace = 1; }; + + # index will be incremented again at the end of the loop, which will bring us past the link's >. + $index = $endIndex; + }; + }; + } + + elsif ($textBlocks->[$index] eq $closingTag) + { + my $tagType = $self->TagType($textBlocks, $index); + + if ($tagType == POSSIBLE_CLOSING_TAG) + { + # There needs to be something between the tags for them to count. + if ($index == $beginningIndex + 1) + { return -1; } + else + { + # Success! + + if ($hasWhitespaceRef) + { $$hasWhitespaceRef = $hasWhitespace; }; + + return $index; + }; + } + + # If there are two opening tags of the same type, the first becomes literal and the next becomes part of a tag. + elsif ($tagType == POSSIBLE_OPENING_TAG) + { return -1; } + } + + elsif ($hasWhitespaceRef && !$hasWhitespace) + { + if ($textBlocks->[$index] =~ /[ \t\n]/) + { $hasWhitespace = 1; }; + }; + + $index++; + }; + + # Hit the end of the text blocks if we're here. + return -1; + }; + + +1; diff --git a/docs/tool/Modules/NaturalDocs/Parser/ParsedTopic.pm b/docs/tool/Modules/NaturalDocs/Parser/ParsedTopic.pm new file mode 100644 index 00000000..a08d65ad --- /dev/null +++ b/docs/tool/Modules/NaturalDocs/Parser/ParsedTopic.pm @@ -0,0 +1,253 @@ +############################################################################### +# +# Package: NaturalDocs::Parser::ParsedTopic +# +############################################################################### +# +# A class for parsed topics of source files. Also encompasses some of the <TopicType>-specific behavior. +# +############################################################################### + +# This file is part of Natural Docs, which is Copyright (C) 2003-2008 Greg Valure +# Natural Docs is licensed under the GPL + +use strict; +use integer; + +package NaturalDocs::Parser::ParsedTopic; + + +############################################################################### +# Group: Implementation + +# +# Constants: Members +# +# The object is a blessed arrayref with the following indexes. +# +# TYPE - The <TopicType>. +# TITLE - The title of the topic. +# PACKAGE - The package <SymbolString> the topic appears in, or undef if none. +# USING - An arrayref of additional package <SymbolStrings> available to the topic via "using" statements, or undef if +# none. +# PROTOTYPE - The prototype, if it exists and is applicable. +# SUMMARY - The summary, if it exists. +# BODY - The body of the topic, formatted in <NDMarkup>. Some topics may not have bodies, and if not, this +# will be undef. +# LINE_NUMBER - The line number the topic appears at in the file. +# IS_LIST - Whether the topic is a list. +# +use NaturalDocs::DefineMembers 'TYPE', 'TITLE', 'PACKAGE', 'USING', 'PROTOTYPE', 'SUMMARY', 'BODY', + 'LINE_NUMBER', 'IS_LIST'; +# DEPENDENCY: New() depends on the order of these constants, and that this class is not inheriting any members. + + +# +# Architecture: Title, Package, and Symbol Behavior +# +# Title, package, and symbol behavior is a little awkward so it deserves some explanation. Basically you set them according to +# certain rules, but you get computed values that try to hide all the different scoping situations. +# +# Normal Topics: +# +# Set them to the title and package as they appear. "Function" and "PkgA.PkgB" will return "Function" for the title, +# "PkgA.PkgB" for the package, and "PkgA.PkgB.Function" for the symbol. +# +# In the rare case that a title has a separator symbol it's treated as inadvertant, so "A vs. B" in "PkgA.PkgB" still returns just +# "PkgA.PkgB" for the package even though if you got it from the symbol it can be seen as "PkgA.PkgB.A vs". +# +# Scope Topics: +# +# Set the title normally and leave the package undef. So "PkgA.PkgB" and undef will return "PkgA.PkgB" for the title as well +# as for the package and symbol. +# +# The only time you should set the package is when you have full language support and they only documented the class with +# a partial title. So if you documented "PkgA.PkgB" with just "PkgB", you want to set the package to "PkgA". This +# will return "PkgB" as the title for presentation and will return "PkgA.PkgB" for the package and symbol, which is correct. +# +# Always Global Topics: +# +# Set the title and package normally, do not set the package to undef. So "Global" and "PkgA.PkgB" will return "Global" as +# the title, "PkgA.PkgB" as the package, and "Global" as the symbol. +# +# Um, yeah...: +# +# So does this suck? Yes, yes it does. But the suckiness is centralized here instead of having to be handled everywhere these +# issues come into play. Just realize there are a certain set of rules to follow when you *set* these variables, and the results +# you see when you *get* them are computed rather than literal. +# + + +############################################################################### +# Group: Functions + +# +# Function: New +# +# Creates a new object. +# +# Parameters: +# +# type - The <TopicType>. +# title - The title of the topic. +# package - The package <SymbolString> the topic appears in, or undef if none. +# using - An arrayref of additional package <SymbolStrings> available to the topic via "using" statements, or undef if +# none. +# prototype - The prototype, if it exists and is applicable. Otherwise set to undef. +# summary - The summary of the topic, if any. +# body - The body of the topic, formatted in <NDMarkup>. May be undef, as some topics may not have bodies. +# lineNumber - The line number the topic appears at in the file. +# isList - Whether the topic is a list topic or not. +# +# Returns: +# +# The new object. +# +sub New #(type, title, package, using, prototype, summary, body, lineNumber, isList) + { + # DEPENDENCY: This depends on the order of the parameter list being the same as the constants, and that there are no + # members inherited from a base class. + + my $package = shift; + + my $object = [ @_ ]; + bless $object, $package; + + if (defined $object->[USING]) + { $object->[USING] = [ @{$object->[USING]} ]; }; + + return $object; + }; + + +# Function: Type +# Returns the <TopicType>. +sub Type + { return $_[0]->[TYPE]; }; + +# Function: SetType +# Replaces the <TopicType>. +sub SetType #(type) + { $_[0]->[TYPE] = $_[1]; }; + +# Function: IsList +# Returns whether the topic is a list. +sub IsList + { return $_[0]->[IS_LIST]; }; + +# Function: SetIsList +# Sets whether the topic is a list. +sub SetIsList + { $_[0]->[IS_LIST] = $_[1]; }; + +# Function: Title +# Returns the title of the topic. +sub Title + { return $_[0]->[TITLE]; }; + +# Function: SetTitle +# Replaces the topic title. +sub SetTitle #(title) + { $_[0]->[TITLE] = $_[1]; }; + +# +# Function: Symbol +# +# Returns the <SymbolString> defined by the topic. It is fully resolved and does _not_ need to be joined with <Package()>. +# +# Type-Specific Behavior: +# +# - If the <TopicType> is always global, the symbol will be generated from the title only. +# - Everything else's symbols will be generated from the title and the package passed to <New()>. +# +sub Symbol + { + my ($self) = @_; + + my $titleSymbol = NaturalDocs::SymbolString->FromText($self->[TITLE]); + + if (NaturalDocs::Topics->TypeInfo($self->Type())->Scope() == ::SCOPE_ALWAYS_GLOBAL()) + { return $titleSymbol; } + else + { + return NaturalDocs::SymbolString->Join( $self->[PACKAGE], $titleSymbol ); + }; + }; + + +# +# Function: Package +# +# Returns the package <SymbolString> that the topic appears in. +# +# Type-Specific Behavior: +# +# - If the <TopicType> has scope, the package will be generated from both the title and the package passed to <New()>, not +# just the package. +# - If the <TopicType> is always global, the package will be the one passed to <New()>, even though it isn't part of it's +# <Symbol()>. +# - Everything else's package will be what was passed to <New()>, even if the title has separator symbols in it. +# +sub Package + { + my ($self) = @_; + + # Headerless topics may not have a type yet. + if ($self->Type() && NaturalDocs::Topics->TypeInfo($self->Type())->Scope() == ::SCOPE_START()) + { return $self->Symbol(); } + else + { return $self->[PACKAGE]; }; + }; + + +# Function: SetPackage +# Replaces the package the topic appears in. This will behave the same way as the package parameter in <New()>. Later calls +# to <Package()> will still be generated according to its type-specific behavior. +sub SetPackage #(package) + { $_[0]->[PACKAGE] = $_[1]; }; + +# Function: Using +# Returns an arrayref of additional scope <SymbolStrings> available to the topic via "using" statements, or undef if none. +sub Using + { return $_[0]->[USING]; }; + +# Function: SetUsing +# Replaces the using arrayref of sope <SymbolStrings>. +sub SetUsing #(using) + { $_[0]->[USING] = $_[1]; }; + +# Function: Prototype +# Returns the prototype if one is defined. Will be undef otherwise. +sub Prototype + { return $_[0]->[PROTOTYPE]; }; + +# Function: SetPrototype +# Replaces the function or variable prototype. +sub SetPrototype #(prototype) + { $_[0]->[PROTOTYPE] = $_[1]; }; + +# Function: Summary +# Returns the topic summary, if it exists, formatted in <NDMarkup>. +sub Summary + { return $_[0]->[SUMMARY]; }; + +# Function: Body +# Returns the topic's body, formatted in <NDMarkup>. May be undef. +sub Body + { return $_[0]->[BODY]; }; + +# Function: SetBody +# Replaces the topic's body, formatted in <NDMarkup>. May be undef. +sub SetBody #(body) + { + my ($self, $body) = @_; + $self->[BODY] = $body; + }; + +# Function: LineNumber +# Returns the line the topic appears at in the file. +sub LineNumber + { return $_[0]->[LINE_NUMBER]; }; + + +1; |