[Mapbender-commits] r4275 - in trunk/build: . NaturalDocs-1.4 NaturalDocs-1.4/Config NaturalDocs-1.4/Help NaturalDocs-1.4/Help/documenting NaturalDocs-1.4/Help/example NaturalDocs-1.4/Help/images NaturalDocs-1.4/Help/images/header NaturalDocs-1.4/Help/images/menu NaturalDocs-1.4/Help/javascript NaturalDocs-1.4/Info NaturalDocs-1.4/Info/images NaturalDocs-1.4/JavaScript NaturalDocs-1.4/Modules NaturalDocs-1.4/Modules/NaturalDocs NaturalDocs-1.4/Modules/NaturalDocs/Builder NaturalDocs-1.4/Modules/NaturalDocs/ClassHierarchy NaturalDocs-1.4/Modules/NaturalDocs/ImageReferenceTable NaturalDocs-1.4/Modules/NaturalDocs/Languages NaturalDocs-1.4/Modules/NaturalDocs/Languages/Advanced NaturalDocs-1.4/Modules/NaturalDocs/Languages/Prototype NaturalDocs-1.4/Modules/NaturalDocs/Menu NaturalDocs-1.4/Modules/NaturalDocs/Parser NaturalDocs-1.4/Modules/NaturalDocs/Project NaturalDocs-1.4/Modules/NaturalDocs/Settings NaturalDocs-1.4/Modules/NaturalDocs/SourceDB NaturalDocs-1.4/Modules/NaturalDocs/SymbolTable NaturalDocs-1.4/Modules/NaturalDocs/Topics NaturalDocs-1.4/Styles

svn_mapbender at osgeo.org svn_mapbender at osgeo.org
Wed Jul 1 11:30:01 EDT 2009


Author: christoph
Date: 2009-07-01 11:30:00 -0400 (Wed, 01 Jul 2009)
New Revision: 4275

Added:
   trunk/build/NaturalDocs-1.4/
   trunk/build/NaturalDocs-1.4/Config/
   trunk/build/NaturalDocs-1.4/Config/Languages.txt
   trunk/build/NaturalDocs-1.4/Config/Topics.txt
   trunk/build/NaturalDocs-1.4/Help/
   trunk/build/NaturalDocs-1.4/Help/customizinglanguages.html
   trunk/build/NaturalDocs-1.4/Help/customizingtopics.html
   trunk/build/NaturalDocs-1.4/Help/documenting.html
   trunk/build/NaturalDocs-1.4/Help/documenting/
   trunk/build/NaturalDocs-1.4/Help/documenting/reference.html
   trunk/build/NaturalDocs-1.4/Help/documenting/walkthrough.html
   trunk/build/NaturalDocs-1.4/Help/example/
   trunk/build/NaturalDocs-1.4/Help/example/Default.css
   trunk/build/NaturalDocs-1.4/Help/example/NaturalDocs.js
   trunk/build/NaturalDocs-1.4/Help/examples.css
   trunk/build/NaturalDocs-1.4/Help/images/
   trunk/build/NaturalDocs-1.4/Help/images/header/
   trunk/build/NaturalDocs-1.4/Help/images/header/background.png
   trunk/build/NaturalDocs-1.4/Help/images/header/leftside.png
   trunk/build/NaturalDocs-1.4/Help/images/header/logo.png
   trunk/build/NaturalDocs-1.4/Help/images/header/overbody.png
   trunk/build/NaturalDocs-1.4/Help/images/header/overbodybg.png
   trunk/build/NaturalDocs-1.4/Help/images/header/overleftmargin.png
   trunk/build/NaturalDocs-1.4/Help/images/header/overmenu.png
   trunk/build/NaturalDocs-1.4/Help/images/header/overmenubg.png
   trunk/build/NaturalDocs-1.4/Help/images/header/rightside.png
   trunk/build/NaturalDocs-1.4/Help/images/logo.gif
   trunk/build/NaturalDocs-1.4/Help/images/menu/
   trunk/build/NaturalDocs-1.4/Help/images/menu/about.png
   trunk/build/NaturalDocs-1.4/Help/images/menu/background.png
   trunk/build/NaturalDocs-1.4/Help/images/menu/bottomleft.png
   trunk/build/NaturalDocs-1.4/Help/images/menu/bottomright.png
   trunk/build/NaturalDocs-1.4/Help/images/menu/community.png
   trunk/build/NaturalDocs-1.4/Help/images/menu/customizing.png
   trunk/build/NaturalDocs-1.4/Help/images/menu/using.png
   trunk/build/NaturalDocs-1.4/Help/index.html
   trunk/build/NaturalDocs-1.4/Help/javascript/
   trunk/build/NaturalDocs-1.4/Help/javascript/BrowserStyles.js
   trunk/build/NaturalDocs-1.4/Help/javascript/PNGHandling.js
   trunk/build/NaturalDocs-1.4/Help/keywords.html
   trunk/build/NaturalDocs-1.4/Help/languages.html
   trunk/build/NaturalDocs-1.4/Help/menu.html
   trunk/build/NaturalDocs-1.4/Help/output.html
   trunk/build/NaturalDocs-1.4/Help/running.html
   trunk/build/NaturalDocs-1.4/Help/styles.css
   trunk/build/NaturalDocs-1.4/Help/styles.html
   trunk/build/NaturalDocs-1.4/Help/troubleshooting.html
   trunk/build/NaturalDocs-1.4/Info/
   trunk/build/NaturalDocs-1.4/Info/CSSGuide.txt
   trunk/build/NaturalDocs-1.4/Info/File Parsing.txt
   trunk/build/NaturalDocs-1.4/Info/HTMLTestCases.pm
   trunk/build/NaturalDocs-1.4/Info/Languages.txt
   trunk/build/NaturalDocs-1.4/Info/NDMarkup.txt
   trunk/build/NaturalDocs-1.4/Info/Symbol Management.txt
   trunk/build/NaturalDocs-1.4/Info/images/
   trunk/build/NaturalDocs-1.4/Info/images/Logo.png
   trunk/build/NaturalDocs-1.4/JavaScript/
   trunk/build/NaturalDocs-1.4/JavaScript/NaturalDocs.js
   trunk/build/NaturalDocs-1.4/License-GPL.txt
   trunk/build/NaturalDocs-1.4/Modules/
   trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/
   trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/BinaryFile.pm
   trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Builder.pm
   trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Builder/
   trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Builder/Base.pm
   trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Builder/FramedHTML.pm
   trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Builder/HTML.pm
   trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Builder/HTMLBase.pm
   trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/ClassHierarchy.pm
   trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/ClassHierarchy/
   trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/ClassHierarchy/Class.pm
   trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/ClassHierarchy/File.pm
   trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/ConfigFile.pm
   trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Constants.pm
   trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/DefineMembers.pm
   trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Error.pm
   trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/File.pm
   trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/ImageReferenceTable.pm
   trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/ImageReferenceTable/
   trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/ImageReferenceTable/Reference.pm
   trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/ImageReferenceTable/String.pm
   trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Languages.pm
   trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Languages/
   trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Languages/ActionScript.pm
   trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Languages/Ada.pm
   trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Languages/Advanced.pm
   trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Languages/Advanced/
   trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Languages/Advanced/Scope.pm
   trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Languages/Advanced/ScopeChange.pm
   trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Languages/Base.pm
   trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Languages/CSharp.pm
   trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Languages/PLSQL.pm
   trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Languages/Pascal.pm
   trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Languages/Perl.pm
   trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Languages/Prototype.pm
   trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Languages/Prototype/
   trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Languages/Prototype/Parameter.pm
   trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Languages/Simple.pm
   trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Languages/Tcl.pm
   trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Menu.pm
   trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Menu/
   trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Menu/Entry.pm
   trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/NDMarkup.pm
   trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Parser.pm
   trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Parser/
   trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Parser/JavaDoc.pm
   trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Parser/Native.pm
   trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Parser/ParsedTopic.pm
   trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Project.pm
   trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Project/
   trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Project/ImageFile.pm
   trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Project/SourceFile.pm
   trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/ReferenceString.pm
   trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Settings.pm
   trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Settings/
   trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Settings/BuildTarget.pm
   trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/SourceDB.pm
   trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/SourceDB/
   trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/SourceDB/Extension.pm
   trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/SourceDB/File.pm
   trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/SourceDB/Item.pm
   trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/SourceDB/ItemDefinition.pm
   trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/SourceDB/WatchedFileDefinitions.pm
   trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/StatusMessage.pm
   trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/SymbolString.pm
   trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/SymbolTable.pm
   trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/SymbolTable/
   trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/SymbolTable/File.pm
   trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/SymbolTable/IndexElement.pm
   trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/SymbolTable/Reference.pm
   trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/SymbolTable/ReferenceTarget.pm
   trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/SymbolTable/Symbol.pm
   trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/SymbolTable/SymbolDefinition.pm
   trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Topics.pm
   trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Topics/
   trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Topics/Type.pm
   trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Version.pm
   trunk/build/NaturalDocs-1.4/NaturalDocs
   trunk/build/NaturalDocs-1.4/NaturalDocs.bat
   trunk/build/NaturalDocs-1.4/Styles/
   trunk/build/NaturalDocs-1.4/Styles/Default.css
   trunk/build/NaturalDocs-1.4/Styles/Roman.css
   trunk/build/NaturalDocs-1.4/Styles/Small.css
Log:
updated_documentation

Added: trunk/build/NaturalDocs-1.4/Config/Languages.txt
===================================================================
--- trunk/build/NaturalDocs-1.4/Config/Languages.txt	                        (rev 0)
+++ trunk/build/NaturalDocs-1.4/Config/Languages.txt	2009-07-01 15:30:00 UTC (rev 4275)
@@ -0,0 +1,286 @@
+Format: 1.4
+
+# This is the main Natural Docs languages file.  If you change anything here,
+# it will apply to EVERY PROJECT you use Natural Docs on.  If you'd like to
+# change something for just one project, edit the Languages.txt in its project
+# directory instead.
+
+
+#-------------------------------------------------------------------------------
+# SYNTAX:
+#
+# Unlike other Natural Docs configuration files, in this file all comments
+# MUST be alone on a line.  Some languages deal with the # character, so you
+# cannot put comments on the same line as content.
+#
+# Also, all lists are separated with spaces, not commas, again because some
+# languages may need to use them.
+#
+# Language: [name]
+#    Defines a new language.  Its name can use any characters.
+#
+#    The language Shebang Script is special.  It's entry is only used for
+#    extensions, and files with those extensions have their shebang (#!) lines
+#    read to determine the real language of the file.  Extensionless files are
+#    always treated this way.
+#
+#    The language Text File is also special.  It's treated as one big comment
+#    so you can put Natural Docs content in them without special symbols.  Also,
+#    if you don't specify a package separator, ignored prefixes, or enum value
+#    behavior, it will copy those settings from the language that is used most
+#    in the source tree.
+#
+# Extensions: [extension] [extension] ...
+#    Defines the file extensions of the language's source files.  You can use *
+#    to mean any undefined extension.
+#
+# Shebang Strings: [string] [string] ...
+#    Defines a list of strings that can appear in the shebang (#!) line to
+#    designate that it's part of the language.
+#
+# Ignore Prefixes in Index: [prefix] [prefix] ...
+# Ignore [Topic Type] Prefixes in Index: [prefix] [prefix] ...
+#    Specifies prefixes that should be ignored when sorting symbols in an
+#    index.  Can be specified in general or for a specific topic type.
+#
+#------------------------------------------------------------------------------
+# For basic language support only:
+#
+# Line Comments: [symbol] [symbol] ...
+#    Defines a space-separated list of symbols that are used for line comments,
+#    if any.
+#
+# Block Comments: [opening sym] [closing sym] [opening sym] [closing sym] ...
+#    Defines a space-separated list of symbol pairs that are used for block
+#    comments, if any.
+#
+# Package Separator: [symbol]
+#    Defines the default package separator symbol.  The default is a dot.
+#
+# [Topic Type] Prototype Enders: [symbol] [symbol] ...
+#    When defined, Natural Docs will attempt to get a prototype from the code
+#    immediately following the topic type.  It stops when it reaches one of
+#    these symbols.  Use \n for line breaks.
+#
+# Line Extender: [symbol]
+#    Defines the symbol that allows a prototype to span multiple lines if
+#    normally a line break would end it.
+#
+# Enum Values: [global|under type|under parent]
+#    Defines how enum values are referenced.  The default is global.
+#    global       - Values are always global, referenced as 'value'.
+#    under type   - Values are under the enum type, referenced as
+#               'package.enum.value'.
+#    under parent - Values are under the enum's parent, referenced as
+#               'package.value'.
+#
+# Perl Package: [perl package]
+#    Specifies the Perl package used to fine-tune the language behavior in ways
+#    too complex to do in this file.
+#
+#------------------------------------------------------------------------------
+# For full language support only:
+#
+# Full Language Support: [perl package]
+#    Specifies the Perl package that has the parsing routines necessary for full
+#    language support.
+#
+#-------------------------------------------------------------------------------
+
+# The following languages MUST be defined in this file:
+#
+#    Text File, Shebang Script
+
+# If you add a language that you think would be useful to other developers
+# and should be included in Natural Docs by default, please e-mail it to
+# languages [at] naturaldocs [dot] org.
+
+
+Language: Text File
+
+   Extension: txt
+
+
+Language: Shebang Script
+
+   Extension: cgi
+
+
+Language: C/C++
+
+   Extensions: c cpp h hpp cxx hxx
+   Ignore Function Prefix in Index: ~
+   Line Comment: //
+   Block Comment: /* */
+   Package Separator: ::
+   Enum Values: Under parent
+   Class Prototype Enders: ; {
+   Function Prototype Enders: ; {
+   Variable Prototype Enders: ; =
+
+
+Language: C#
+
+   Extension: cs
+   Ignore Prefix in Index: @
+   Full Language Support: NaturalDocs::Languages::CSharp
+
+
+Language: Java
+
+   Extension: java
+   Line Comment: //
+   Block Comment: /* */
+   Enum Values: Under type
+   Function Prototype Ender: {
+   Variable Prototype Enders: ; =
+
+
+Language: JavaScript
+
+   Extension: js
+   Line Comment: //
+   Block Comment: /* */
+   Enum Values: Under type
+   Function Prototype Ender: {
+   Variable Prototype Enders: ; =
+
+
+Language: Perl
+
+   Extensions: pl pm
+   Shebang String: perl
+   Ignore Variable Prefixes in Index: $ @ % *
+   Full Language Support: NaturalDocs::Languages::Perl
+
+
+Language: Python
+
+   Extension: py
+   Shebang String: python
+   Line Comment: #
+   Function Prototype Ender: :
+   Variable Prototype Ender: =
+   Line Extender: \
+
+
+Language: PHP
+
+   Extensions: inc php php3 php4 phtml
+   Shebang String: php
+   Ignore Variable Prefix in Index: $
+   Line Comments: // #
+   Block Comment: /* */
+   Function Prototype Enders: ; {
+   Variable Prototype Enders: ; =
+
+
+Language: SQL
+
+   Extension: sql
+   Line Comment: --
+   Block Comment: /* */
+   Enum Values: Global
+   Function Prototype Enders: , ; ) as As AS is Is IS
+   Variable Prototype Enders: , ; ) := default Default DEFAULT
+   Database Index Prototype Enders: , ; )
+   Database Trigger Prototype Enders: begin Begin BEGIN as As AS
+   Perl Package: NaturalDocs::Languages::PLSQL
+
+
+Language: Visual Basic
+
+   Extensions: vb vbs bas cls frm
+   Line Comment: '
+   Enum Values: Under type
+   Function Prototype Ender: \n
+   Variable Prototype Enders: \n =
+   Line Extender: _
+
+
+Language: Pascal
+
+   Extension: pas
+   Line Comment: //
+   Block Comments: { } (* *)
+   Function Prototype Ender: ;
+   Variable Prototype Enders: ; =
+   Perl Package: NaturalDocs::Languages::Pascal
+
+
+Language: Assembly
+
+   Extension: asm
+   Line Comment: ;
+   Variable Prototype Ender: \n
+   Line Extender: \
+
+
+Language: Ada
+
+   Extensions: ada ads adb
+   Line Comment: --
+   Function Prototype Enders: ; is Is IS
+   Variable Prototype Enders: ; :=
+   Perl Package: NaturalDocs::Languages::Ada
+
+
+Language: Tcl
+
+   Extensions: tcl exp
+   Shebang Strings: tclsh wish expect
+   Line Comment: #
+   Package Separator: ::
+   Function Prototype Enders: ; {
+   Variable Prototype Enders: ; \n
+   Line Extender: \
+   Perl Package: NaturalDocs::Languages::Tcl
+
+
+Language: Ruby
+
+   Extension: rb
+   Shebang String: ruby
+   Ignore Variable Prefixes in Index: $ @ @@
+   Line Comment: #
+   Enum Values: Under parent
+   Function Prototype Enders: ; \n
+   Variable Prototype Enders: ; \n =
+   Line Extender: \
+
+
+Language: Makefile
+
+   Extensions: mk mak make
+   Line Comment: #
+
+
+Language: ActionScript
+
+   Extensions: as mxml
+   Full Language Support: NaturalDocs::Languages::ActionScript
+
+
+Language: ColdFusion
+
+   Extensions: cfm cfml cfc
+   Line Comment: //
+   Block Comments: <!--- ---> /* */
+   Function Prototype Enders: { <
+
+
+Language: R
+
+   Extension: r
+   Line Comment: #
+   Function Prototype Enders: { ;
+   Variable Prototype Enders: <- = ; \n
+
+
+Language: Fortran
+
+   Extensions: f90 f95 f03
+   Line Comment: !
+   Function Prototype Ender: \n
+   Variable Prototype Enders: \n = =>
+   Line Extender: &

Added: trunk/build/NaturalDocs-1.4/Config/Topics.txt
===================================================================
--- trunk/build/NaturalDocs-1.4/Config/Topics.txt	                        (rev 0)
+++ trunk/build/NaturalDocs-1.4/Config/Topics.txt	2009-07-01 15:30:00 UTC (rev 4275)
@@ -0,0 +1,382 @@
+Format: 1.4
+
+# This is the main Natural Docs topics file.  If you change anything here, it
+# will apply to EVERY PROJECT you use Natural Docs on.  If you'd like to
+# change something for just one project, edit the Topics.txt in its project
+# directory instead.
+
+
+#-------------------------------------------------------------------------------
+# SYNTAX:
+#
+# Topic Type: [name]
+#    Creates a new topic type.  Each type gets its own index and behavior
+#    settings.  Its name can have letters, numbers, spaces, and these
+#    charaters: - / . '
+#
+#    The Enumeration type is special.  It's indexed with Types but its members
+#    are indexed with Constants according to the rules in Languages.txt.
+#
+# Plural: [name]
+#    Sets the plural name of the topic type, if different.
+#
+# Keywords:
+#    [keyword]
+#    [keyword], [plural keyword]
+#    ...
+#    Defines a list of keywords for the topic type.  They may only contain
+#    letters, numbers, and spaces and are not case sensitive.  Plural keywords
+#    are used for list topics.
+#
+# Index: [yes|no]
+#    Whether the topics get their own index.  Defaults to yes.  Everything is
+#    included in the general index regardless of this setting.
+#
+# Scope: [normal|start|end|always global]
+#    How the topics affects scope.  Defaults to normal.
+#    normal        - Topics stay within the current scope.
+#    start         - Topics start a new scope for all the topics beneath it,
+#                    like class topics.
+#    end           - Topics reset the scope back to global for all the topics
+#                    beneath it.
+#    always global - Topics are defined as global, but do not change the scope
+#                    for any other topics.
+#
+# Class Hierarchy: [yes|no]
+#    Whether the topics are part of the class hierarchy.  Defaults to no.
+#
+# Page Title If First: [yes|no]
+#    Whether the topic's title becomes the page title if it's the first one in
+#    a file.  Defaults to no.
+#
+# Break Lists: [yes|no]
+#    Whether list topics should be broken into individual topics in the output.
+#    Defaults to no.
+#
+# Can Group With: [type], [type], ...
+#    Defines a list of topic types that this one can possibly be grouped with.
+#    Defaults to none.
+#-------------------------------------------------------------------------------
+
+# The following topics MUST be defined in this file:
+#
+#    Generic, Class, Interface, Section, File, Group, Function, Variable,
+#    Property, Type, Constant, Enumeration, Event, Delegate
+
+# If you add something that you think would be useful to other developers
+# and should be included in Natural Docs by default, please e-mail it to
+# topics [at] naturaldocs [dot] org.
+
+
+Topic Type: Generic
+
+   Index: No
+   Keywords:
+      topic, topics
+      about, list
+
+
+Topic Type: Class
+
+   Plural: Classes
+   Scope: Start
+   Class Hierarchy: Yes
+   Page Title If First: Yes
+   Can Group With: Interfaces
+
+   Keywords:
+      class, classes
+      structure, structures
+      struct, structs
+      package, packages
+      namespace, namespaces
+
+
+Topic Type: Interface
+
+   Plural: Interfaces
+   Scope: Start
+   Class Hierarchy: Yes
+   Page Title If First: Yes
+   Can Group With: Classes
+
+   Keywords:
+      interface, interfaces
+
+
+Topic Type: Section
+
+   Plural: Sections
+   Index: No
+   Scope: End
+   Page Title If First: Yes
+
+   Keywords:
+      section
+      title
+
+
+Topic Type: File
+
+   Plural: Files
+   Scope: Always global
+   Page Title If First: Yes
+
+   Keywords:
+      file, files
+      program, programs
+      script, scripts
+      document, documents
+      doc, docs
+      header, headers
+
+
+Topic Type: Group
+
+   Plural: Groups
+   Index: No
+
+   Keywords:
+      group
+
+
+Topic Type: Function
+
+   Plural: Functions
+   Break Lists: Yes
+   Can Group With: Properties
+
+   Keywords:
+      function, functions
+      func, funcs
+      procedure, procedures
+      proc, procs
+      routine, routines
+      subroutine, subroutines
+      sub, subs
+      method, methods
+      callback, callbacks
+      constructor, constructors
+      destructor, destructors
+      operator, operators
+
+
+Topic Type: Variable
+
+   Plural: Variables
+   Can Group With: Types, Constants, Macros, Enumerations
+
+   Keywords:
+      variable, variables
+      var, vars
+      integer, integers
+      int, ints
+      uint, uints
+      long, longs
+      ulong, ulongs
+      short, shorts
+      ushort, ushorts
+      byte, bytes
+      ubyte, ubytes
+      sbyte, sbytes
+      float, floats
+      double, doubles
+      real, reals
+      decimal, decimals
+      scalar, scalars
+      array, arrays
+      arrayref, arrayrefs
+      hash, hashes
+      hashref, hashrefs
+      bool, bools
+      boolean, booleans
+      flag, flags
+      bit, bits
+      bitfield, bitfields
+      field, fields
+      pointer, pointers
+      ptr, ptrs
+      reference, references
+      ref, refs
+      object, objects
+      obj, objs
+      character, characters
+      wcharacter, wcharacters
+      char, chars
+      wchar, wchars
+      string, strings
+      wstring, wstrings
+      str, strs
+      wstr, wstrs
+      handle, handles
+
+
+Topic Type: Property
+
+   Plural: Properties
+   Can Group With: Functions
+
+   Keywords:
+      property, properties
+      prop, props
+
+
+Topic Type: Type
+
+   Plural: Types
+   Can Group With: Variables, Constants, Macros, Enumerations
+
+   Keywords:
+      type, types
+      typedef, typedefs
+
+
+Topic Type: Constant
+
+   Plural: Constants
+   Can Group With: Variables, Types, Macros, Enumerations
+
+   Keywords:
+      constant, constants
+      const, consts
+
+
+Topic Type: Enumeration
+
+   Plural: Enumerations
+   Index: No
+   Can Group With: Variables, Types, Macros, Constants
+
+   Keywords:
+      enum, enums
+      enumeration, enumerations
+
+
+Topic Type: Event
+
+   Plural: Events
+   Keywords:
+      event, events
+
+
+Topic Type: Delegate
+
+   Plural: Delegates
+   Keywords:
+      delegate, delegates
+
+
+Topic Type: Macro
+
+   Plural: Macros
+   Can Group With: Variables, Types, Constants
+
+   Keywords:
+      define, defines
+      def, defs
+      macro, macros
+
+
+Topic Type: Database
+
+   Plural: Databases
+   Page Title If First: Yes
+
+   Keywords:
+      database, databases
+      db, dbs
+
+
+Topic Type: Database Table
+
+   Plural: Database Tables
+   Scope: Start
+   Page Title If First: Yes
+
+   Keywords:
+      table, tables
+      database table, database tables
+      databasetable, databasetables
+      db table, db tables
+      dbtable, dbtables
+
+
+Topic Type: Database View
+
+   Plural: Database Views
+   Scope: Start
+   Page Title If First: Yes
+
+   Keywords:
+      view, views
+      database view, database views
+      databaseview, databaseviews
+      db view, db views
+      dbview, dbviews
+
+
+Topic Type: Database Index
+
+   Plural: Database Indexes
+   Keywords:
+      index, indexes
+      index, indices
+      database index, database indexes
+      database index, database indices
+      databaseindex, databaseindexes
+      databaseindex, databaseindices
+      db index, db indexes
+      db index, db indices
+      dbindex, dbindexes
+      dbindex, dbindices
+      key, keys
+      database key, database keys
+      databasekey, databasekeys
+      db key, db keys
+      dbkey, dbkeys
+      primary key, primary keys
+      primarykey, primarykeys
+      database primary key, database primary keys
+      databaseprimarykey, databaseprimarykeys
+      db primary key, db primary keys
+      dbprimarykey, dbprimarykeys
+
+
+Topic Type: Database Cursor
+
+   Plural: Database Cursors
+   Keywords:
+      cursor, cursors
+      database cursor, database cursors
+      databasecursor, databasecursors
+      db cursor, db cursors
+      dbcursor, dbcursors
+
+
+Topic Type: Database Trigger
+
+   Plural: Database Triggers
+   Keywords:
+      trigger, triggers
+      database trigger, database triggers
+      databasetrigger, databasetriggers
+      db trigger, db triggers
+      dbtrigger, dbtriggers
+
+
+Topic Type: Cookie
+
+   Plural: Cookies
+   Scope: Always global
+
+   Keywords:
+      cookie, cookies
+
+
+Topic Type: Build Target
+
+   Plural: Build Targets
+   Keywords:
+      target, targets
+      build target, build targets
+      buildtarget, buildtargets

Added: trunk/build/NaturalDocs-1.4/Help/customizinglanguages.html
===================================================================
--- trunk/build/NaturalDocs-1.4/Help/customizinglanguages.html	                        (rev 0)
+++ trunk/build/NaturalDocs-1.4/Help/customizinglanguages.html	2009-07-01 15:30:00 UTC (rev 4275)
@@ -0,0 +1,52 @@
+
+
+<html><head><title>Customizing Natural Docs Languages</title><link rel=stylesheet type="text/css" href="styles.css"><link rel=stylesheet type="text/css" href="examples.css"><style type="text/css"><!--
+
+
+        .InMainFile {
+            padding: 1ex 2ex;
+            margin: 1em 0;
+            font: italic 9pt Verdana, sans-serif;
+            line-height: 150%;
+            background-color: #F8F8F8;
+            }
+        .InMainFile code {
+            font-size: 9pt;
+            }
+
+        .EnumTable {
+            margin: .5em 5ex;
+            }
+        .EnumOption {
+            font-weight: bold;
+            padding-right: 2ex;
+            }
+
+    
+--></style><script language=JavaScript src="javascript/PNGHandling.js"></script><script language=JavaScript src="javascript/BrowserStyles.js"></script></head><body marginwidth=0 marginheight=0 leftmargin=0 topmargin=0><script language=JavaScript><!--
+OpeningBrowserTags();// --></script>
+
+<!-- saved from url=(0026)http://www.naturaldocs.org -->
+
+<table width=100% border=0 cellspacing=0 cellpadding=0><tr><td colspan=3 class=Header><table width=100% border=0 cellspacing=0 cellpadding=0><tr><td><img src="images/header/leftside.png" width=30 height=75><a href="index.html"><img src="images/header/logo.png" width=524 height=75 alt="Natural Docs"></a></td><td align=right><img src="images/header/rightside.png" width=30 height=75></td></tr></table></td></tr><tr><td><img src="images/header/overleftmargin.png" width=10 height=6></td><td class=SideMenuTop><img src="images/header/overmenu.png" width=14 height=6></td><td class=BodyTop><img src="images/header/overbody.png" width=24 height=6></td></tr><tr><td></td><td class=SideMenu nowrap><div class=SideMenuSection><div class=SideMenuTitle><img src="images/menu/about.png" width=52 height=13 alt="About"></div><div class=SideMenuBody><a href="languages.html" class=SideMenuEntry>Language Support</a><a href="output.html" class=SideMenuEntry>Output Formats</a></div></div><div class=SideMenuSection><div class=SideMenuTitle><img src="images/menu/using.png" width=45 height=13 alt="Using"></div><div class=SideMenuBody><a href="documenting.html" class=SideMenuEntry>Documenting<br>Your Code</a><a href="keywords.html" class=SideMenuEntry>Keywords</a><a href="running.html" class=SideMenuEntry>Running</a><a href="troubleshooting.html" class=SideMenuEntry>Troubleshooting</a></div></div><div class=SideMenuSection><div class=SideMenuTitle><img src="images/menu/customizing.png" width=96 height=13 alt="Customizing"></div><div class=SideMenuBody><a href="menu.html" class=SideMenuEntry>Organizing the Menu</a><a href="styles.html" class=SideMenuEntry>CSS Styles</a><a href="customizingtopics.html" class=SideMenuEntry>Topics and Keywords</a><span class=SideMenuEntry id=SelectedSideMenuEntry>Languages, Indexes,<br>and Prototypes</span></div></div><div class=SideMenuSection><div class=SideMenuTitle><img src="images/menu/community.png" width=86 height=13 alt="Community"></div><div class=SideMenuBody><a href="http://www.naturaldocs.org/" class=SideMenuEntry>Web Site</a><a href="http://www.naturaldocs.org/mailinglist.html" class=SideMenuEntry>Mailing Lists</a><a href="http://www.naturaldocs.org/messageboards.html" class=SideMenuEntry>Message Boards</a><a href="http://www.naturaldocs.org/bugs.html" class=SideMenuEntry>Bugs and<br>Feature Requests</a></div></div></td><td class=Body width=100%><div class=PageTitle>Customizing Languages</div><div class=TOC><a href="#LanguagesTxt">Languages.txt</a> &middot; <a href="#FileExtensions">File Extensions</a> &middot; <a href="#AddingLanguages">Adding Languages</a> &middot; <a href="#Prototypes">Prototypes</a><br><a href="#IndexPrefixes">Index Prefixes</a> &middot; <a href="#SpecialLanguages">Special Languages</a> &middot; <a href="#SyntaxReference">Syntax Reference</a></div><div class=Topic><a name="LanguagesTxt"></a><div class=TopicTitle>Languages.txt</div><p>Natural Docs has two files called <code>Languages.txt</code>: one in its Config directory, and one in <a href="running.html#CommandLine">your project directory.</a>&nbsp; These control the language, index prefix, and prototype features of Natural Docs.</p><p>You should edit the one in your project directory whenever possible.&nbsp; It keeps your changes separate and easier to manage, plus you don&rsquo;t have to reapply them whenever you upgrade.&nbsp; Editing the one in Natural Docs&rsquo; Config directory would be better only if you&rsquo;re using Natural Docs with a lot of projects and would like the changes to apply everywhere.</p><p>Note that unlike other Natural Docs configuration files, comments can only appear on their own lines.&nbsp; They cannot appear after something else on a line because settings may need to use the <code>#</code> symbol.&nbsp; Also, all lists are space-separated instead of comma-separated, again because some settings may need to use the comma symbol.</p></div><div class=Topic><a name=FileExtensions></a><div class=TopicTitle>File Extensions</div><p>If Natural Docs doesn&rsquo;t recognize a file extension you use for your code, it&rsquo;s not a problem.&nbsp; You can alter the language definition from your project file to add them.</p><pre class=Example>Alter Language: <i>[your language]</i>
+   Add Extensions: cxx hxx</pre><p>On the other hand, if it&rsquo;s scanning some files you don&rsquo;t want it to scan, you can exclude extensions as well.&nbsp; Just add this to the top of your file:</p><pre class=Example>Ignore Extensions: c cpp</pre><p>In this example, Natural Docs will ignore C++ source files, thus only scanning the headers.</p></div><div class=Topic><a name=AddingLanguages></a><div class=TopicTitle>Adding Languages</div><p>You can add <a href="languages.html">basic language support</a> for any programming language just by editing these configuration files.&nbsp; Here are the most important settings:</p><pre class=Example>Language: Fictional
+
+   Extensions: fsrc fhdr
+   Shebang Strings: fictional
+   Line Comment: //
+   Block Comment: /* */
+   Package Separator: ::</pre><p>This tells Natural Docs that any files with the .fsrc or .fhdr extensions are part of our fictional programming language.&nbsp; Also, any .cgi or extensionless files that have &ldquo;fictional&rdquo; in the shebang (<code>#!</code>) line are part of it as well.&nbsp; Line comments start with <code>//</code> and block comments appear between <code>/*</code> and <code>*/</code>.&nbsp; The default package separator is <code>::</code>.&nbsp; Not too hard, huh?</p><p>You can also add settings to <a href="#IndexPrefixes">ignore prefixes in the index</a> and <a href="#Prototypes">detect prototypes</a>, but those are dealt with in their own sections on this page.</p></div><div class=Topic><a name=Prototypes></a><div class=TopicTitle>Prototypes</div><p>So you&rsquo;ve <a href="#AddingLanguages">added a new language</a> and want to detect prototypes.&nbsp; Or perhaps you <a href="customizingtopics.html#AddingTopicTypes">added a custom topic type</a> and want to detect prototypes for that as well.&nbsp; Here&rsquo;s an example of the properties you need:</p><pre class=Example>Function Prototype Enders: ; {
+Variable Prototype Enders: ; =</pre><p>The algorithm for finding prototypes is very simple, yet it works really well in practice.&nbsp; All the code following the comment is grabbed until it reaches an ender symbol or another comment.&nbsp; Ender symbols appearing inside parenthesis, brackets, braces, or angle brackets don&rsquo;t count.&nbsp; If it reaches an ender symbol and somewhere in that code is the topic title, the code is accepted as the prototype.</p><p>So in the example above, variables end at semicolons (the end of the declaration) or equal signs (the default value expression, which we don&rsquo;t want to include.)&nbsp; Since the Natural Docs comment for the variable should have appeared right before the definition, that leaves us with the name and type.&nbsp; Functions are handled similarly: they end at a semicolon (the end of a predeclaration) or an opening brace (the beginning of the body) leaving us with the name, parameters, and return type.</p><p>You can do this with any topic type, including custom ones.&nbsp; Any prototypes that look like they have parameters will be formatted as such automatically.</p><div class=SubTopic>Line Breaks</div><p>For some languages, line breaks are significant.&nbsp; To have them end a prototype, use <code>\n</code>.&nbsp; If it has an extender symbol that allows the code to continue on the next line, you can specify that as well.</p><pre class=Example>Function Prototype Ender: \n
+Variable Prototype Ender: \n =
+Line Extender: _</pre><div class=SubTopic>Colors</div><p>If you&rsquo;re collecting prototypes for a custom topic type, they will not automatically get their own background color like the other types have.&nbsp; <a href="styles.html#CommonCustomizations">You have to define it via CSS.</a></p></div><div class=Topic><a name=IndexPrefixes></a><div class=TopicTitle>Index Prefixes</div><p>Natural Docs has the ability to ignore prefixes in the indexes.&nbsp; This is necessary because in certain languages, variables are prefixed with <code>$</code> or other symbols and we don&rsquo;t want them to get all grouped together under the symbols heading.&nbsp; Instead, they appear in the sidebar and are sorted as if they&rsquo;re not there.</p><div class=NDIndex><table border=0 cellspacing=0 cellpadding=0><tr><td class=IHeading id=IFirstHeading>A</td><td></td></tr><tr><td class=ISymbolPrefix id=IFirstSymbolPrefix>&nbsp;</td><td class=IEntry><span class=ISymbol>AddProperty</span>, <span class=IParent>SomeClass</span></td></tr><tr><td class=ISymbolPrefix>$</td><td class=IEntry><span class=ISymbol>amount</span></td></tr><tr><td class=ISymbolPrefix id=ILastSymbolPrefix>&nbsp;</td><td class=IEntry><span class=ISymbol>Average</span></td></tr></table></div><p>However, we can take advantage of this simply to get around coding conventions.&nbsp; Suppose you prefix all your class names with C.&nbsp; They&rsquo;d all form one gigantic group under C in the index.&nbsp; If you want, you can have it ignored so CCat, CDog, and CMouse get filed under C, D, and M instead.&nbsp; Just add this to your languages file:</p><pre class=Example>Alter Language: <i>[your language]</i>
+   Add Ignored Class Prefix in Index: C</pre><p>Now C is ignored in your indexes:</p><div class=NDIndex><table border=0 cellspacing=0 cellpadding=0><tr><td class=IHeading id=IFirstHeading>A</td><td></td></tr><tr><td class=ISymbolPrefix id=IFirstSymbolPrefix>C</td><td class=IEntry><span class=ISymbol>Account</span></td></tr><tr><td class=ISymbolPrefix>C</td><td class=IEntry><span class=ISymbol>AccountHolder</span></td></tr><tr><td class=ISymbolPrefix>&nbsp;</td><td class=IEntry><span class=ISymbol>AddProperty</span>, <span class=IParent>SomeClass</span></td></tr><tr><td class=ISymbolPrefix>$</td><td class=IEntry><span class=ISymbol>amount</span></td></tr><tr><td class=ISymbolPrefix id=ILastSymbolPrefix>&nbsp;</td><td class=IEntry><span class=ISymbol>Average</span></td></tr></table></div><p>You can include any number of prefixes and can do this for any topic type.&nbsp; So if you have a bunch of functions that start with <code>COM_</code> and <code>DB_</code>, you can ignore them too:</p><pre class=Example>Alter Language: <i>[your language]</i>
+   Add Ignored Class Prefix in Index: C
+   Add Ignored Function Prefixes in Index: COM_ DB_</pre></div><div class=Topic><a name=SpecialLanguages></a><div class=TopicTitle>Special Languages</div><p>There are two languages with special properties: Shebang Script and Text File.</p><p>Shebang Script allows you to define the file extensions where the language is really determined by the shebang (<code>#!</code>) line within it.&nbsp; For example, .cgi files.&nbsp; If Natural Docs finds a .cgi file, it sees that it&rsquo;s a Shebang Script so it opens it up to parse it&rsquo;s shebang line.&nbsp; It then searches it for substrings defined by other languages&rsquo; <code>Shebang String</code> settings to find out what language it really is.&nbsp; Files with no extension are always treated this way.</p><p>With Text File, the entire file is treated like a comment.&nbsp; There are no comment symbols required, you can just put Natural Docs content there in plain text.&nbsp; The most important setting is <code>Extensions</code>.</p><p>However, since it is possible to document classes, functions, etc. in text files, they also have their own <code>Package Separator</code> and <code>Ignored <i>[type]</i> Prefixes in Index</code> settings.&nbsp; To make things easier on you, by default it copies these settings from whichever language has the most source files in your project.&nbsp; You can override this by manually setting them, but you shouldn&rsquo;t need to.</p></div><div class=Topic><a name=SyntaxReference></a><div class=TopicTitle>Syntax Reference</div><p>Unlike other Natural Docs configuration files, comments can only appear on their own lines.&nbsp; They cannot appear after something else on a line because settings may need to use the <code>#</code> symbol.&nbsp; Likewise, lists are separated with spaces instead of commas because commas themselves may need to appear on the list.</p><p>Singular and plural forms are generally both supported, so you can write <code>Extension</code> or <code>Extensions</code>.&nbsp; It doesn&rsquo;t matter if they match how many items are set.&nbsp; Also, you can use either <code>Ignore</code> or <code>Ignored</code>.</p><pre class=Example>Ignore Extensions: <i>[extension] [extension]</i> ...</pre><p>Causes the listed file extensions to be ignored, even if they were previously defined to be part of a language.&nbsp; The list is space-separated.&nbsp; ex. &ldquo;<code>Ignore Extensions: cvs txt</code>&rdquo;</p><pre class=Example>Language: <i>[name]</i>
+Alter Language: <i>[name]</i></pre><p>Creates a new language or alters an existing one.&nbsp; Names can use any characters.&nbsp; Note the <a href="#SpecialLanguages">special behavior for languages named Shebang Script and Text File</a>.</p><p>If you&rsquo;re altering an existing language and a property has an <code>[Add/Replace]</code> form, you have to specify whether you&rsquo;re adding to or replacing the list if that property has already been defined.</p><div class=SubTopic>General Language Properties</div><pre class=Example>Extensions: <i>[extension] [extension]</i> ...
+<i>[Add/Replace]</i> Extensions: <i>[extension] [extension]</i> ...</pre><p>Defines file extensions for the language&rsquo;s source files.&nbsp; The list is space-separated.&nbsp; ex. &ldquo;<code>Extensions: c cpp</code>&rdquo;.&nbsp; You can use extensions that were previously used by another language to redefine them.&nbsp; You can use <code>*</code> to specify all undefined extensions.</p><pre class=Example>Shebang Strings: <i>[string] [string]</i> ...
+<i>[Add/Replace]</i> Shebang Strings: <i>[string] [string]</i> ...</pre><p>Defines a list of strings that can appear in the shebang (<code>#!</code>) line to designate that it&rsquo;s part of this language.&nbsp; They can appear anywhere in the line, so <code>php</code> will work for &ldquo;<code>#!/user/bin/php4</code>&rdquo;.&nbsp; You can use strings that were previously used by another language to redefine them.</p><pre class=Example>Ignore Prefixes in Index: <i>[prefix] [prefix]</i> ...
+Ignore <i>[type]</i> Prefixes in Index: <i>[prefix] [prefix]</i> ...
+
+<i>[Add/Replace]</i> Ignored Prefixes in Index: <i>[prefix] [prefix]</i> ...
+<i>[Add/Replace]</i> Ignored <i>[type]</i> Prefixes in Index: <i>[prefix] [prefix]</i> ...</pre><p>Specifies prefixes that should be ignored when sorting symbols for an index.&nbsp; Can be specified in general or for a specific topic type.&nbsp; The prefixes will still appear, the symbols will just be sorted as if they&rsquo;re not there.&nbsp; For example, specifying <code>ADO_</code> for functions will mean that <code>ADO_DoSomething</code> will appear under D instead of A.</p><div class=SubTopic>Basic Language Support Properties</div><p>These attributes are only available for languages with basic language support.</p><pre class=Example>Line Comments: <i>[symbol] [symbol]</i> ...</pre><p>Defines a space-separated list of symbols that are used for line comments, if any.&nbsp; ex. &ldquo;<code>Line Comment: //</code>&rdquo;.</p><pre class=Example>Block Comments: <i>[opening symbol] [closing symbol] [o.s.] [c.s.]</i> ...</pre><p>Defines a space-separated list of symbol pairs that are used for block comments, if any.&nbsp; ex. &ldquo;<code>Block Comment: /* */</code>&rdquo;.</p><pre class=Example>Enum Values: <i>[global|under type|under parent]</i></pre><p>Defines the behavior of enum values.&nbsp; The default is global.</p><table border=0 cellspacing=0 cellpadding=0 class=EnumTable><tr><td class=EnumOption>Global</td><td>Enum values are always global and will be referenced as &ldquo;Value&rdquo;.</td></tr><tr><td class=EnumOption>Under Type</td><td>Enum values appear under the type and will be referenced as &ldquo;Package.Enum.Value&rdquo;.</td></tr><tr><td class=EnumOption>Under Parent</td><td>Enum values appear under the parent and will be referenced as &ldquo;Package.Value&rdquo;</td></tr></table><pre class=Example><i>[type]</i> Prototype Enders: <i>[symbol] [symbol]</i> ...</pre><p>When defined, Natural Docs will attempt to collect prototypes from the code following the specified topic type.&nbsp; It grabs code until the first ender symbol or the next Natural Docs comment, and if it contains the topic name, it serves as its prototype.&nbsp; Use <code>\n</code> to specify a line break.&nbsp; ex. &ldquo;<code>Function Prototype Enders: { ;</code>&rdquo;, &ldquo;<code>Variable Prototype Enders: = ;</code>&rdquo;. </p><pre class=Example>Line Extender: <i>[symbol]</i></pre><p>Defines the symbol that allows a prototype to span multiple lines if normally a line break would end it.</p><pre class=Example>Perl Package: <i>[perl package]</i></pre><p>Specifies the Perl package used to fine-tune the language behavior in ways too complex to do in this file.</p><div class=SubTopic>Full Language Support Properties</div><p>These attributes are only available for languages with full language support.</p><pre class=Example>Full Language Support: <i>[perl package]</i></pre><p>Specifies the Perl package that has the parsing routines necessary for full language support.</p></div></td></tr><tr><td></td><td class=SideMenuBottom><table width=100% border=0 cellspacing=0 cellpadding=0><tr><td class=SideMenuBottomLeft><img src="images/menu/bottomleft.png" width=18 height=19></td><td class=SideMenuBottomRight><img src="images/menu/bottomright.png" width=18 height=19></td></tr></table></td><td class=BodyBottom>Copyright &copy; 2003-2008 Greg Valure</td></tr></table><script language=JavaScript><!--
+ClosingBrowserTags();// --></script></body></html>
\ No newline at end of file

Added: trunk/build/NaturalDocs-1.4/Help/customizingtopics.html
===================================================================
--- trunk/build/NaturalDocs-1.4/Help/customizingtopics.html	                        (rev 0)
+++ trunk/build/NaturalDocs-1.4/Help/customizingtopics.html	2009-07-01 15:30:00 UTC (rev 4275)
@@ -0,0 +1,74 @@
+
+
+<html><head><title>Customizing Natural Docs Topics</title><link rel=stylesheet type="text/css" href="styles.css"><style type="text/css"><!--
+
+
+        .InMainFile {
+            padding: 1ex 2ex;
+            margin: 1em 0;
+            font: italic 9pt Verdana, sans-serif;
+            line-height: 150%;
+            background-color: #F8F8F8;
+            }
+        .InMainFile code {
+            font-size: 9pt;
+            }
+
+        .ScopeTable {
+            margin: .5em 5ex;
+            }
+        .ScopeOption {
+            font-weight: bold;
+            padding-right: 2ex;
+            }
+
+    
+--></style><script language=JavaScript src="javascript/PNGHandling.js"></script><script language=JavaScript src="javascript/BrowserStyles.js"></script></head><body marginwidth=0 marginheight=0 leftmargin=0 topmargin=0><script language=JavaScript><!--
+OpeningBrowserTags();// --></script>
+
+<!-- saved from url=(0026)http://www.naturaldocs.org -->
+
+<table width=100% border=0 cellspacing=0 cellpadding=0><tr><td colspan=3 class=Header><table width=100% border=0 cellspacing=0 cellpadding=0><tr><td><img src="images/header/leftside.png" width=30 height=75><a href="index.html"><img src="images/header/logo.png" width=524 height=75 alt="Natural Docs"></a></td><td align=right><img src="images/header/rightside.png" width=30 height=75></td></tr></table></td></tr><tr><td><img src="images/header/overleftmargin.png" width=10 height=6></td><td class=SideMenuTop><img src="images/header/overmenu.png" width=14 height=6></td><td class=BodyTop><img src="images/header/overbody.png" width=24 height=6></td></tr><tr><td></td><td class=SideMenu nowrap><div class=SideMenuSection><div class=SideMenuTitle><img src="images/menu/about.png" width=52 height=13 alt="About"></div><div class=SideMenuBody><a href="languages.html" class=SideMenuEntry>Language Support</a><a href="output.html" class=SideMenuEntry>Output Formats</a></div></div><div class=SideMenuSection><div class=SideMenuTitle><img src="images/menu/using.png" width=45 height=13 alt="Using"></div><div class=SideMenuBody><a href="documenting.html" class=SideMenuEntry>Documenting<br>Your Code</a><a href="keywords.html" class=SideMenuEntry>Keywords</a><a href="running.html" class=SideMenuEntry>Running</a><a href="troubleshooting.html" class=SideMenuEntry>Troubleshooting</a></div></div><div class=SideMenuSection><div class=SideMenuTitle><img src="images/menu/customizing.png" width=96 height=13 alt="Customizing"></div><div class=SideMenuBody><a href="menu.html" class=SideMenuEntry>Organizing the Menu</a><a href="styles.html" class=SideMenuEntry>CSS Styles</a><span class=SideMenuEntry id=SelectedSideMenuEntry>Topics and Keywords</span><a href="customizinglanguages.html" class=SideMenuEntry>Languages, Indexes,<br>and Prototypes</a></div></div><div class=SideMenuSection><div class=SideMenuTitle><img src="images/menu/community.png" width=86 height=13 alt="Community"></div><div class=SideMenuBody><a href="http://www.naturaldocs.org/" class=SideMenuEntry>Web Site</a><a href="http://www.naturaldocs.org/mailinglist.html" class=SideMenuEntry>Mailing Lists</a><a href="http://www.naturaldocs.org/messageboards.html" class=SideMenuEntry>Message Boards</a><a href="http://www.naturaldocs.org/bugs.html" class=SideMenuEntry>Bugs and<br>Feature Requests</a></div></div></td><td class=Body width=100%><div class=PageTitle>Customizing Topics</div><div class=TOC><a href="#Topicstxt">Topics.txt</a> &middot; <a href="#TopicTypesVsKeywords">Topic Types vs. Keywords</a> &middot; <a href="#AddingTopicTypes">Adding Topic Types</a><br><a href="#ChangingKeywords">Changing Keywords</a> &middot; <a href="#AlteringBehavior">Altering Behavior</a> &middot; <a href="#SyntaxReference">Syntax Reference</a></div><div class=Topic><a name="Topicstxt"></a><div class=TopicTitle>Topics.txt</div><p>Natural Docs has two files called <code>Topics.txt</code>: one in its Config directory, and one in <a href="running.html#CommandLine">your project directory.</a>&nbsp; These control the topic behavior and keywords Natural Docs uses.</p><p>You should edit the one in your project directory whenever possible.&nbsp; It keeps your changes separate and easier to manage, plus you don&rsquo;t have to reapply them whenever you upgrade.&nbsp; Editing the one in Natural Docs&rsquo; Config directory would be better only if you&rsquo;re using Natural Docs with a lot of projects and would like the changes to apply everywhere.</p></div><div class=Topic><a name="TopicTypesVsKeywords"></a><div class=TopicTitle>Topic Types vs. Keywords</div><p>It&rsquo;s important to understand the difference between topic types and keywords.&nbsp; Topic types have their own indexes and behavior settings.&nbsp; You&rsquo;ll reference them by name when dealing with indexes in the <a href="menu.html">menu file</a> or prototype detection in the <a href="customizinglanguages.html">language file</a>, but not when documenting your code unless you make their names keywords as well.</p><p>You use keywords when documenting your code.&nbsp; There can be many keywords per topic type, and they are completely interchangable.</p><p>Suppose you document a class with the <code>Class</code> keyword and a struct with <code>Struct</code>.&nbsp; They are both keywords for the Class topic type by default, so they will appear in the same index.&nbsp; If you wanted structs to have their own index, you would <a href="#AddingTopicTypes">add a topic type for structs</a> and <a href="#ChangingKeywords">change the <code>Struct</code> keyword to point to it.</a></p></div><div class=Topic><a name="AddingTopicTypes"></a><div class=TopicTitle>Adding Topic Types</div><p>If you want to be able to document something in Natural Docs doesn&rsquo;t handle by default, you want to create your own topic type for it.&nbsp; Let&rsquo;s say you&rsquo;re working on a video game and you want to document all the sound effects because you want to keep track of what each one is for and have an index of them.&nbsp; You&rsquo;d add this to your topics file:</p><pre class=Example>Topic Type: Sound Effect
+   Plural: Sound Effects
+   Keywords:
+      sound
+      sound effect
+</pre><p>Sound effects can now be documented with the <code>sound</code> or <code>sound effect</code> keywords, and they&rsquo;ll get their own index.&nbsp; The <code>Plural</code> line just specifies the plural name of the topic type.&nbsp; It isn&rsquo;t required, but Natural Docs will use it in some places where the plural would sound more natural, like when grouping topics or naming indexes on the menu.</p><p>Here are a couple of other things you may want to add:</p><pre class=Example>Topic Type: Sound Effect
+   Plural: Sound Effects
+   Scope: Always Global
+   Keywords:
+      sound, sounds
+      sound effect, sound effects
+</pre><p>You can set the <a href="documenting/reference.html#KeywordsTopicsAndScope">scope behavior</a> of the topic type.&nbsp; Your options are:</p><table border=0 cellspacing=0 cellpadding=0 class=ScopeTable><tr><td class=ScopeOption>Normal</td><td>Topics stay within the current scope.</td></tr><tr><td class=ScopeOption>Start</td><td>Topics start a new scope for all the topics beneath it, like class topics.</td></tr><tr><td class=ScopeOption>End</td><td>Topics reset the scope back to global for all the topics beneath it.</td></tr><tr><td class=ScopeOption>Always Global</td><td>Topics are defined as global, but do not change the scope for any other topics.</td></tr></table><p>Here we set it to <code>Always Global</code> so that if we document one as part of a class, it will still be global yet will not break the class&rsquo; scope.&nbsp; In other words, we can always link to it with just its name instead of needing something like <code>&lt;Class.Sound&gt;</code>.</p><p>The other thing we did was add plural keywords, which you do by using a comma after an existing keyword.&nbsp; These keywords are used for <a href="documenting/reference.html#ListTopics">list topics</a> so we don&rsquo;t have to document each one individually with the full syntax.</p><p>There are more options, these are just the most important ones.&nbsp; See the <a href="#SyntaxReference">full syntax reference</a> for the rest.</p><a name="Prototypes"></a><div class="SubTopic">Prototypes</div><p>If you&rsquo;d like to collect prototypes for your new topic type, you have to do that by <a href="customizinglanguages.html#Prototypes">editing <code>Languages.txt</code></a>.</p></div><div class=Topic><a name="ChangingKeywords"></a><div class=TopicTitle>Changing Keywords</div><a name="AddingAndChanging"></a><div class="SubTopic First">Adding and Changing</div><p>If you&rsquo;re <a href="#AddingTopicTypes">defining your own topic type</a> or editing the main topics file, you simply add to the keywords list:</p><pre class=Example>Topic Type: Sound Effect
+   Keywords:
+      sound, sounds
+      sound effect, sound effects
+</pre><p>It doesn&rsquo;t matter if the keyword was previously defined for a different topic type.&nbsp; Just define it again and the definition will change.</p><p>If you want to add keywords to one of the main topic types from the project file, use <code>Alter Topic Type</code> instead:</p><pre class=Example>Alter Topic Type: General
+   Keywords:
+      note
+      notes
+</pre><p>Natural Docs will keep a list of the main file&rsquo;s topic types in your project file so that you can do this easily.</p><a name="Ignoring"></a><div class="SubTopic">Ignoring</div><p>Sometimes a keyword just gets in the way.&nbsp; It&rsquo;s too common in your comments and Natural Docs keeps accidentally picking them up as topics when that isn&rsquo;t what you wanted.&nbsp; You can get rid of keywords completely by either deleting them from the main file or putting this in your project file:</p><pre class=Example>Ignore Keywords:
+   about
+   title
+</pre><p>If you only have a few, you can use this syntax as well:</p><pre class=Example>Ignore Keywords: note, notes, title
+</pre></div><div class=Topic><a name="AlteringBehavior"></a><div class=TopicTitle>Altering Behavior</div><p>You can change the behavior of any topic type defined in the main file via your project file.&nbsp; Just use <code>Alter Topic Type</code> and redefine any property.</p><pre class=Example>Alter Topic Type: Constant
+   Scope: Always Global
+</pre><p>Natural Docs will keep a list of the main file&rsquo;s topic types in your project file so you can do this easily.&nbsp; See the <a href="#SyntaxReference">syntax reference</a> below for a full list of your options.</p></div><div class=Topic><a name="SyntaxReference"></a><div class=TopicTitle>Syntax Reference</div><pre class=Example>Ignore Keywords: <i>[keyword]</i>, <i>[keyword]</i> ...
+   <i>[keyword]</i>
+   <i>[keyword]</i>, <i>[keyword]</i>
+   ...
+</pre><p>Ignores the keywords so that they&rsquo;re not recognized as Natural Docs topics anymore.&nbsp; Can be specified as a list on the same line and/or following like a normal Keywords section.</p><pre class=Example>Topic Type: <i>[name]</i>
+Alter Topic Type: <i>[name]</i>
+</pre><p>Creates a new topic type or alters an existing one.&nbsp; The name can only contain letters, numbers, spaces, and these characters: <code>. - &lsquo; /</code>.&nbsp; It isn&rsquo;t case sensitive, although the original case is remembered for presentation.</p><p>There are a number of default types that must be defined in the main file, but they will be listed there since it may change between versions.&nbsp; The default types can have their keywords or behaviors changed, though, either by editing the default file or by overriding them in the user file.</p><a name="Properties"></a><div class="SubTopic">Properties</div><pre class=Example>Plural: <i>[name]</i>
+</pre><p>Specifies the plural name of the topic type.&nbsp; Defaults to the singular name.&nbsp; Has the same restrictions as the topic type name.</p><pre class=Example>Index: <i>[yes|no]</i>
+</pre><p>Whether the topic type gets an index.&nbsp; Defaults to yes.</p><pre class=Example>Scope: <i>[normal|start|end|always global]</i>
+</pre><p>How the topic affects scope.&nbsp; Defaults to normal.</p><table border=0 cellspacing=0 cellpadding=0 class=ScopeTable><tr><td class=ScopeOption>Normal</td><td>Topics stay within the current scope.</td></tr><tr><td class=ScopeOption>Start</td><td>Topics start a new scope for all the topics beneath it, like class topics.</td></tr><tr><td class=ScopeOption>End</td><td>Topics reset the scope back to global for all the topics beneath it.</td></tr><tr><td class=ScopeOption>Always Global</td><td>Topics are defined as global, but do not change the scope for any other topics.</td></tr></table><pre class=Example>Class Hierarchy: <i>[yes|no]</i>
+</pre><p>Whether the topic is part of the class hierarchy.&nbsp; Defaults to no.</p><pre class=Example>Page Title if First: <i>[yes|no]</i>
+</pre><p>Whether the title of this topic becomes the page title if it is the first topic in a file.&nbsp; Defaults to no.</p><pre class=Example>Break Lists: <i>[yes|no]</i>
+</pre><p>Whether <a href="documenting/reference.html#ListTopics">list topics</a> should be broken into individual topics in the output.&nbsp; Defaults to no.</p><pre class=Example>Can Group With: <i>[topic type]</i>, <i>[topic type]</i>, ...
+</pre><p>Lists the topic types that can be grouped with this one in the output.&nbsp; If two or more topic types often appear together, like Functions and Properties, this will allow them to be grouped together under one heading if it would cause too many groups otherwise.</p><pre class=Example>Keywords:
+   <i>[keyword]</i>
+   <i>[keyword]</i>, <i>[plural keyword]</i>
+   ...
+</pre><p>A list of the topic type&rsquo;s keywords.&nbsp; Each line after the heading is the keyword and optionally its plural form.&nbsp; This continues until the next line in &ldquo;<code>keyword: value</code>&rdquo; format.</p><ul><li>Keywords can only have letters, numbers, and spaces.&nbsp; No punctuation or symbols are allowed.</li><li>Keywords are not case sensitive.</li><li>Subsequent keyword sections add to the list.&nbsp; They don&rsquo;t replace it.</li><li>Keywords can be redefined by appearing in later keyword sections.</li></ul></div></td></tr><tr><td></td><td class=SideMenuBottom><table width=100% border=0 cellspacing=0 cellpadding=0><tr><td class=SideMenuBottomLeft><img src="images/menu/bottomleft.png" width=18 height=19></td><td class=SideMenuBottomRight><img src="images/menu/bottomright.png" width=18 height=19></td></tr></table></td><td class=BodyBottom>Copyright &copy; 2003-2008 Greg Valure</td></tr></table><script language=JavaScript><!--
+ClosingBrowserTags();// --></script></body></html>
\ No newline at end of file

Added: trunk/build/NaturalDocs-1.4/Help/documenting/reference.html
===================================================================
--- trunk/build/NaturalDocs-1.4/Help/documenting/reference.html	                        (rev 0)
+++ trunk/build/NaturalDocs-1.4/Help/documenting/reference.html	2009-07-01 15:30:00 UTC (rev 4275)
@@ -0,0 +1,146 @@
+
+
+<html><head><title>Documenting Your Code: Reference - Natural Docs</title><link rel=stylesheet type="text/css" href="../styles.css"><link rel=stylesheet type="text/css" href="../examples.css"><style type="text/css"><!--
+
+
+        .KeywordList                        { margin: 1em 5ex }
+        .KeywordList td                { padding-bottom: 1em }
+
+                .KeywordListKeyword                {  font-weight: bold; white-space: nowrap  }
+                .KeywordListDescription                {  padding-left: 5ex  }
+                .KeywordListSynonyms                {  font-weight: normal; font-size: 8pt; font-style: italic }
+
+                .KeywordListSynonyms a:link,
+                .KeywordListSynonyms a:visited,
+                .KeywordListSynonyms a:hover                { color: #808080 }
+
+        
+--></style><script language=JavaScript src="../javascript/PNGHandling.js"></script><script language=JavaScript src="../javascript/BrowserStyles.js"></script><script language=JavaScript src="../example/NaturalDocs.js"></script></head><body marginwidth=0 marginheight=0 leftmargin=0 topmargin=0><script language=JavaScript><!--
+OpeningBrowserTags();// --></script>
+
+<!-- saved from url=(0026)http://www.naturaldocs.org -->
+
+<table width=100% border=0 cellspacing=0 cellpadding=0><tr><td colspan=3 class=Header><table width=100% border=0 cellspacing=0 cellpadding=0><tr><td><img src="../images/header/leftside.png" width=30 height=75><a href="../index.html"><img src="../images/header/logo.png" width=524 height=75 alt="Natural Docs"></a></td><td align=right><img src="../images/header/rightside.png" width=30 height=75></td></tr></table></td></tr><tr><td><img src="../images/header/overleftmargin.png" width=10 height=6></td><td class=SideMenuTop><img src="../images/header/overmenu.png" width=14 height=6></td><td class=BodyTop><img src="../images/header/overbody.png" width=24 height=6></td></tr><tr><td></td><td class=SideMenu nowrap><div class=SideMenuSection><div class=SideMenuTitle><img src="../images/menu/about.png" width=52 height=13 alt="About"></div><div class=SideMenuBody><a href="../languages.html" class=SideMenuEntry>Language Support</a><a href="../output.html" class=SideMenuEntry>Output Formats</a></div></div><div class=SideMenuSection><div class=SideMenuTitle><img src="../images/menu/using.png" width=45 height=13 alt="Using"></div><div class=SideMenuBody><a href="../documenting.html" class=SideMenuEntry id=SelectedSideMenuEntry>Documenting<br>Your Code</a><a href="../keywords.html" class=SideMenuEntry>Keywords</a><a href="../running.html" class=SideMenuEntry>Running</a><a href="../troubleshooting.html" class=SideMenuEntry>Troubleshooting</a></div></div><div class=SideMenuSection><div class=SideMenuTitle><img src="../images/menu/customizing.png" width=96 height=13 alt="Customizing"></div><div class=SideMenuBody><a href="../menu.html" class=SideMenuEntry>Organizing the Menu</a><a href="../styles.html" class=SideMenuEntry>CSS Styles</a><a href="../customizingtopics.html" class=SideMenuEntry>Topics and Keywords</a><a href="../customizinglanguages.html" class=SideMenuEntry>Languages, Indexes,<br>and Prototypes</a></div></div><div class=SideMenuSection><div class=SideMenuTitle><img src="../images/menu/community.png" width=86 height=13 alt="Community"></div><div class=SideMenuBody><a href="http://www.naturaldocs.org/" class=SideMenuEntry>Web Site</a><a href="http://www.naturaldocs.org/mailinglist.html" class=SideMenuEntry>Mailing Lists</a><a href="http://www.naturaldocs.org/messageboards.html" class=SideMenuEntry>Message Boards</a><a href="http://www.naturaldocs.org/bugs.html" class=SideMenuEntry>Bugs and<br>Feature Requests</a></div></div></td><td class=Body width=100%><div class=PageTitle>Documenting Your Code</div><div class=TOC><a href="#Comments">Comments</a> &middot; <a href="#TextFiles">Text Files</a> &middot; <a href="#KeywordsTopicsAndScope">Keywords, Topics, and Scope</a> &middot; <a href="#Linking">Linking</a><br><a href="#FormattingAndLayout">Formatting and Layout</a> &middot; <a href="#PageTitles">Page Titles</a> &middot; <a href="#Summaries">Summaries</a> &middot; <a href="#JavadocCompatibility">Javadoc Compatibility</a></div><div class=CToolTip id="ttAdd"><div class=CFunction><table border=0 cellspacing=0 cellpadding=0 class=Prototype><tr><td><table border=0 cellspacing=0 cellpadding=0><tr><td class=PBeforeParameters>int Add (</td><td class=PType>int&nbsp;</td><td class=PParameter>x,</td><td></td></tr><tr><td></td><td class=PType>int&nbsp;</td><td class=PParameter>y</td><td class=PAfterParameters>)</td></tr></table></td></tr></table>Adds two integers.</div></div><div class=CToolTip id="ttSubtract"><div class=CFunction><table border=0 cellspacing=0 cellpadding=0 class=Prototype><tr><td><table border=0 cellspacing=0 cellpadding=0><tr><td class=PBeforeParameters>int Subtract (</td><td class=PType>int&nbsp;</td><td class=PParameter>x,</td><td></td></tr><tr><td></td><td class=PType>int&nbsp;</td><td class=PParameter>y</td><td class=PAfterParameters>)</td></tr></table></td></tr></table>Subtracts two integers.</div></div><div class=CToolTip id="ttMultiply"><div class=CFunction><table border=0 cellspacing=0 cellpadding=0 class=Prototype><tr><td><table border=0 cellspacing=0 cellpadding=0><tr><td class=PBeforeParameters>int Multiply (</td><td class=PType>int&nbsp;</td><td class=PParameter>x,</td><td></td></tr><tr><td></td><td class=PType>int&nbsp;</td><td class=PParameter>y</td><td class=PAfterParameters>)</td></tr></table></td></tr></table>Multiplies two integers.</div></div><div class=CToolTip id="ttDivide"><div class=CFunction><table border=0 cellspacing=0 cellpadding=0 class=Prototype><tr><td><table border=0 cellspacing=0 cellpadding=0><tr><td class=PBeforeParameters>int Divide (</td><td class=PType>int&nbsp;</td><td class=PParameter>x,</td><td></td></tr><tr><td></td><td class=PType>int&nbsp;</td><td class=PParameter>y</td><td class=PAfterParameters>)</td></tr></table></td></tr></table>Divides two integers.</div></div><div class=CToolTip id="ttIsEqual"><div class=CFunction><table border=0 cellspacing=0 cellpadding=0 class=Prototype><tr><td><table border=0 cellspacing=0 cellpadding=0><tr><td class=PBeforeParameters>bool IsEqual (</td><td class=PType>int&nbsp;</td><td class=PParameter>x,</td><td></td></tr><tr><td></td><td class=PType>int&nbsp;</td><td class=PParameter>y</td><td class=PAfterParameters>)</td></tr></table></td></tr></table>Returns whether two integers are equal.</div></div><div class=Topic><a name="Comments"></a><div class=TopicTitle>Comments</div><p>There is no special comment style for Natural Docs.&nbsp; You just embed Natural Docs topics into regular comments, and it&rsquo;s pretty tolerant as far as style goes.&nbsp; You can use block comments or string together line comments.&nbsp; The only requirement is that the comments are not on the same line as code.</p><pre class=Example>/* Function: Multiply
+   Multiplies two integers and returns the result. */
+
+// Function: Multiply
+// Multiplies two integers and returns the result.
+</pre><p>Note that when stringing line comments together, blank lines that you want to include in the documentation must start with the comment symbol as well.&nbsp; If a line is completely blank, it&rsquo;s considered the end of the comment and thus the end of the Natural Docs topic.</p><a name="BoxesAndHorizontalLines"></a><div class="SubTopic">Boxes and Horizontal Lines</div><p>Natural Docs can also handle comment boxes and horizontal lines.&nbsp; It doesn&rsquo;t matter what symbols they use.&nbsp; The boxes don&rsquo;t need to be closed on the right side, and they can have different symbols for the edges and corners.</p><pre class=Example>/*
+ * Function: Multiply
+ * Multiplies two integers and returns the result.
+ */
+
+/* +-------------------------------------------------+
+   | Function: Multiply                              |
+   | Multiplies two integers and returns the result. |
+   +-------------------------------------------------+ */
+
+//////////////////////////////////////////////////////////////
+//
+//  Function: Multiply
+//  ------------------
+//
+//  Multiplies two integers together and returns the result.
+//
+//////////////////////////////////////////////////////////////
+</pre><a name="JavadocStyle"></a><div class="SubTopic">Javadoc Style</div><p>If you have <a href="../languages.html">full language support</a>, you can also use Javadoc-style comments to write Natural Docs documentation.&nbsp; To do this you repeat the last symbol on the first line of the comment once.&nbsp; The comment must appear directly above what it&rsquo;s documenting, and you can omit the topic line if you want (the &ldquo;<code>Function: Multiply</code>&rdquo; part.)</p><pre class=Example>/**
+ * Multiplies two integers and returns the result.
+ */
+
+///
+// Multiplies two integers together and returns the result.
+</pre><p>If you omit the topic line and include any Javadoc tags in the comment, like <code>@param</code>, the entire comment will be treated as Javadoc and can only use Javadoc formatting.&nbsp; Otherwise it&rsquo;s treated as a Natural Docs comment and you can use all the formatting options described on this page.&nbsp; You can have both styles in the same source file, but not in the same comment.</p><a name="PerlPOD"></a><div class="SubTopic">Perl POD</div><p>Perl users can also use POD to do block comments.</p><pre class=Example>=begin nd
+
+Function: Multiply
+Multiplies two integers and returns the result.
+
+=cut
+</pre><p>You can also use <code>NaturalDocs</code> or <code>Natural Docs</code> in place of <code>ND</code>.&nbsp; None of them are case sensitive.&nbsp; If for some reason you want to go back to POD documentation instead of using <code>=cut</code>, you can write <code>=end nd</code>.</p><p>There&rsquo;s a second form of just <code>=nd</code> which is offered as a convenience.&nbsp; However, it is <b>not valid POD</b>.&nbsp; Perl will skip over it and execute fine, but POD parsers will give errors and possibly include the unformatted text in the output.&nbsp; Use the longer, valid form unless you know for certain that no one will ever try to run POD on your code.</p><pre class=Example>=nd
+
+Function: Multiply
+Multiplies two integers and returns the result.
+
+=cut
+</pre></div><div class=Topic><a name="TextFiles"></a><div class=TopicTitle>Text Files</div><p>Documentation can also be included in text files.&nbsp; Any file with a .txt extension appearing in the source tree and starting with a topic line will included in the documentation.&nbsp; It will be treated the same as a source file, meaning it will appear in the menu, its topics will be in the indexes, and its topics can be linked to from anywhere in the documentation.&nbsp; The only difference is you don&rsquo;t need comment symbols.</p><p>Remember that the topic line is required to be the first line of content.&nbsp; If the first non-blank line is not in the &ldquo;<code>Function: Multiply</code>&rdquo; format the file will be ignored.&nbsp; An easy way to do this is to use the <code>Title</code> keyword, although all of <a href="../keywords.html">the other ones</a> will work as well.</p><pre class=Example>Title: License
+
+This project is licensed under the GPL.
+</pre><p>This method is convenient for documenting file formats, configuration settings, the general program architecture, or anything else that isn&rsquo;t directly tied to a source file.</p></div><div class=Topic><a name="KeywordsTopicsAndScope"></a><div class=TopicTitle>Keywords, Topics, and Scope</div><p>A topic in Natural Docs starts with a topic line in the format <code>&ldquo;keyword: name&rdquo;</code>.&nbsp; You can have multiple topics per comment as long as you separate them with a blank line.&nbsp; The keywords aren&rsquo;t case sensitive.</p><p>The list of keywords is pretty predictable: Function, Class, Variable, etc.&nbsp; Just use what you&rsquo;re documenting.&nbsp; There are many synonyms as well, so you can use keywords like Func, Procedure, Proc, Method and Constructor.&nbsp; Look at the <a href="../keywords.html">full list of keywords</a> to see everything that&rsquo;s available.</p><p>The <a href="../keywords.html">list of keywords</a> is separated into topic types.&nbsp; Each type gets its own index, and which specific keyword you use doesn&rsquo;t matter.&nbsp; Some also have scoping rules or other behavior as noted.</p><a name="Scope"></a><div class="SubTopic">Scope</div><p>Like the code it&rsquo;s documenting, Natural Docs topics have scope.&nbsp; This mostly has to do with <a href="#Linking">linking</a>: if you&rsquo;re in a class you can link to its members by their name alone, but if you&rsquo;re not, you have to use a notation like <code>class.member</code> or <code>class::member</code>.</p><p>If you have <a href="../languages.html">full language support</a> and are documenting something that appears in the code, the scope will be handled automatically.&nbsp; If you&rsquo;re using text files, have basic language support, or are including a topic that doesn&rsquo;t correspond to something in the code, scoping follows these rules:</p><ul><li>Everything after a class topic (or <a href="../keywords.html">anything that says &ldquo;Starts Scope&rdquo;</a>) is part of that class.</li><li>Everything after a section topic (or <a href="../keywords.html">anything that says &ldquo;Ends Scope&rdquo;</a>) is global again.</li><li>File topics (or <a href="../keywords.html">anything that says &ldquo;Always Global&rdquo;</a>) are global but do not change the scope for what follows.</li><p></ul></p><a name="ListTopics"></a><div class="SubTopic">List Topics</div><p>If you looked at the list, you saw that most of the keywords have plural forms.&nbsp; That&rsquo;s for list topics, which let you document many small things without using the full syntax.&nbsp; Anything that appears in <a href="#DefinitionLists">definition lists</a> within that topic will be treated as if it had its own topic.&nbsp; It will appear in the indexes and be linkable, just like normal topics.</p><p>Function list topics will automatically break apart in the output as well, so it will look the same as if you documented each one individually.</p></div><div class=Topic><a name="Linking"></a><div class=TopicTitle>Linking</div><p>Linking is the one place where Natural Docs has some negative effect on the readability of the comments.&nbsp; The alternative would be to automatically guess where links should be, but systems that do that can sometimes pepper your sentences with unintentional links to functions called &ldquo;is&rdquo; or &ldquo;on&rdquo;.&nbsp; However, the Natural Docs syntax is still as minimal as possible.&nbsp; Simply surround any topic you want to link to with angle brackets.&nbsp; Natural Docs will keep track off all the topics and where they are defined, so you don&rsquo;t need to use HTML-like syntax or remember what file anything is in.&nbsp; Also, if the link can&rsquo;t be resolved to anything, Natural Docs leaves the angle brackets in the output so if something wasn&rsquo;t intended to be a link (such as <code>#include &lt;somefile.h&gt;</code>) it won&rsquo;t be mangled.</p><pre class=Example>Let's link to function &lt;Multiply&gt;.
+</pre><div class=NDContent><p class=CParagraph>Let&rsquo;s link to function <a href="#Example_Class.Multiply" class=LFunction id=link632 onMouseOver="ShowTip(event, 'ttMultiply', 'link632')" onMouseOut="HideTip('ttMultiply')">Multiply</a>.</p></div><p>Links and topic names are case sensitive, regardless of whether the language is or not.</p><p>When linking to functions, it doesn&rsquo;t matter if you include empty parenthesis or not.&nbsp; Both <code>&lt;Function&gt;</code> and <code>&lt;Function()&gt;</code> will work.&nbsp; However, if you documented the function with parameters as part of the name, you will need to include those parameters whenever linking to it.&nbsp; It is recommended that you only include parameters in the topic name if you need to distinguish between two functions with the same name.</p><p>If the topic has a <a href="#Summaries">summary sentence</a>, hovering over the link will give it to you as a tooltip.&nbsp; If the topic has a prototype, that will be included as well.&nbsp; You can try it above.</p><div class="SubTopic">Scope</div><p>If a topic is <a href="#Scope">considered part of a class</a>, they can be linked to using any of the three most common class/member notations:&nbsp; <code>class.member</code>, <code>class::member</code>, and <code>class-&gt;member</code>.&nbsp; Natural Docs will not be confused by <code>&lt;class-&gt;member&gt;</code>.&nbsp; Like in the language itself, if the topic you&rsquo;re writing is in that class&rsquo; scope you can link to it simply as <code>&lt;member&gt;</code>.</p><p>If you have multi-level classes and packages, links can be relative as well.&nbsp; So if you&rsquo;re in <code>Project::UI::Window::Base</code> and you want to link to <code>Project::UI::Button::Base</code>, just using <code>&lt;Button::Base&gt;</code> will work.</p><a name="PluralsAndPossessives"></a><div class="SubTopic">Plurals and Possessives</div><p>To make the documentation easier to write and easier to read in the source file, you can include plurals and possessives inside the angle brackets.&nbsp; In other words, you don&rsquo;t have to use awkward syntax like <code>&lt;Object&gt;s</code>, although that&rsquo;s supported as well.&nbsp; You can simply write <code>&lt;Objects&gt;</code> and it will link to the symbol <code>Object</code> just fine.&nbsp; It can handle any plural and/or possessive form you can throw at it.&nbsp; I&rsquo;m not kidding: <code>Foxes</code>, <code>Fox&rsquo;s</code>, <code>Foxes&rsquo;</code>, <code>Children</code>, <code>Mice</code>, <code>Alumni</code>, <code>Indices</code>, <code>Amoebae</code>, <code>Teeth</code>, just try to trip it up.</p><a name="URLsAndEMail"></a><div class="SubTopic">URLs and E-Mail</div><p>You can also link to URLs and e-mail addresses.&nbsp; It will detect them automatically, but you can also put them in angle brackets if you like.</p><pre class=Example>Visit &lt;http://www.website.com&gt; or send messages to
+email at address.com.
+</pre><div class=NDContent><div class=CFunction><div class=CTopic><div class=CBody><p class=CParagraph>Visit <a href="#" onClick="return false;" class=LURL>http://www.website.com</a> or send messages to <a href="#" onclick="location.href='mai' + 'lto:' + 'em' + 'ail' + '@' + 'addre' + 'ss.com'; return false;" class="LEMail">em<span style="display: none;">.nosp at m.</span>ail<span>@</span>addre<span style="display: none;">.nosp at m.</span>ss.com</a>.</p></div></div></div></div><p>E-mail addresses are protected in a way that should avoid spam crawlers.&nbsp; Although the link above looks and acts like a regular link (try it) the HTML code actually looks like this:</p><pre class=Example>&lt;a href="#"
+ onClick="location.href='mai' + 'lto:' + 'em' + 'ail' + '@'
+          + 'addre' + 'ss.com'; return false;"&gt;
+    em&lt;span style="display: none"&gt;.nosp at m.&lt;/span&gt;ail
+    &lt;span&gt;@&lt;/span&gt;
+    addre&lt;span style="display: none"&gt;.nosp at m.&lt;/span&gt;ss.com
+&lt;/a&gt;
+</pre></div><div class=Topic><a name="FormattingAndLayout"></a><div class=TopicTitle>Formatting and Layout</div><p>You can apply additional formatting and layout to your Natural Docs content, all in ways that will appear very natural in the source code.</p><a name="Paragraphs"></a><div class="SubTopic">Paragraphs</div><p>You can break paragraphs by leaving blank lines between them.&nbsp; So we have this in our content:</p><pre class=Example>The first paragraph blah blah blah blah blah blah blah blah
+blah blah blah blah blah blah blah blah blah blah blah blah
+blah blah blah blah.
+
+The second paragraph blah blah blah blah blah blah blah
+blah blah blah blah blah blah blah blah blah blah blah blah
+blah blah blah blah.
+</pre><p>and we get this in our output:</p><div class=NDContent><div class=CBody><div class=CFunction><div class=CTopic><p class=CParagraph>The first paragraph blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah.</p><p class=CParagraph>The second paragraph blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah.</p></div></div></div></div><a name="BoldAndUnderline"></a><div class="SubTopic">Bold and Underline</div><p>You can apply bold to a stretch of text by surrounding it with asterisks.&nbsp; You can apply underlining by surrounding it with underscores instead.&nbsp; With underlining, it doesn&rsquo;t matter if you use an underscore for every space between words or not; they&rsquo;ll be converted to spaces if you do.</p><pre class=Example>Some *bold text* and some _underlined text_
+and yet _more_underlined_text_.
+</pre><div class=NDContent><div class=CFunction><div class=CTopic><div class=CBody><p class=CParagraph>Some <b>bold text</b> and some <u>underlined text</u> and yet <u>more underlined text</u>.</p></div></div></div></div><a name="Headings"></a><div class="SubTopic">Headings</div><p>You can add headings to your output just by ending a line with a colon and having a blank line above it.</p><pre class=Example>Some text before the heading.
+
+Heading:
+Some text under the heading.
+</pre><div class=NDContent><div class=CFunction><div class=CTopic><div class=CBody><p class=CParagraph>Some text before the heading.</p><h4 class=CHeading>Heading</h4><p class=CParagraph>Some text under the heading.</p></div></div></div></div><p>You <u>must</u> have a blank line above the heading or it will not work.&nbsp; You can skip the blank after it but not before.</p><a name="BulletLists"></a><div class="SubTopic">Bullet Lists</div><p>You can add bullet lists by starting a line with a dash, an asterisk, an o, or a plus.&nbsp; Bullets can have blank lines between them if you want, and subsequent lines don&rsquo;t have to be indented.&nbsp; You end a list by skipping a line and doing something else.</p><pre class=Example>- Bullet one.
+- Bullet two.
+  Bullet two continued.
+- Bullet three.
+
+Some text after the bullet list.
+
+o Spaced bullet one.
+
+o Spaced bullet two.
+Spaced bullet two continued.
+
+o Spaced bullet three.
+
+Some text after the spaced bullet list.
+</pre><div class=NDContent><div class=CFunction><div class=CTopic><div class=CBody><ul class=CBulletList><li>Bullet one.</li><li>Bullet two.&nbsp; Bullet two continued.</li><li>Bullet three.</li></ul><p class=CParagraph>Some text after the bullet list.</p><ul class=CBulletList><li>Spaced bullet one.</li><li>Spaced bullet two.&nbsp; Spaced bullet two continued.</li><li>Spaced bullet three.</li></ul><p class=CParagraph>Some text after the spaced bullet list.</p></div></div></div></div><a name="DefinitionLists"></a><div class="SubTopic">Definition Lists</div><p>You can add a definition list by using the format below, specifically &ldquo;text space dash space text&rdquo;.&nbsp; Like bullet lists, you can have blank lines between them if you want, subsequent lines don&rsquo;t have to be indented, and you end the list by skipping a line and doing something else.</p><pre class=Example>First  - This is the first item.
+Second - This is the second item.
+         This is more of the second item.
+Third  - This is the third item.
+This is more of the third item.
+
+Some text after the definition list.
+</pre><div class=NDContent><div class=CFunction><div class=CTopic><div class=CBody><table border=0 cellspacing=0 cellpadding=0 class=CDescriptionList><tr><td class=CDLEntry>First</td><td class=CDLDescription>This is the first item.</td></tr><tr><td class=CDLEntry>Second</td><td class=CDLDescription>This is the second item.&nbsp; This is more of the second item.</td></tr><tr><td class=CDLEntry>Third</td><td class=CDLDescription>This is the third item.&nbsp; This is more of the third item.</td></tr></table><p class=CParagraph>Some text after the definition list.</p></div></div></div></div><p>Remember that with definition lists, if you&rsquo;re using the plural form of the keywords each entry can be linked to as if it had its own topic.</p><a name="CodeAndTextDiagrams"></a><div class="SubTopic">Code and Text Diagrams</div><p>You can add example code or text diagrams by starting each line with <code>&gt;</code>, <code>|</code>, or <code>:</code>.&nbsp; If you have a vertical line or text box with the comment, you must separate these symbols from it with a space.</p><pre class=Example>: a = b + c;
+
+&gt;   +-----+     +-----+
+&gt;   |  A  | --> |  B  |
+&gt;   +-----+     +-----+
+&gt;                  |
+&gt;               +-----+
+&gt;               |  C  |
+&gt;               +-----+
+</pre><div class=NDContent><div class=CFunction><div class=CTopic><div class=CBody><pre class=CCode>a = b + c;</pre><pre class=CCode>+-----+     +-----+<br>|  A  | --> |  B  |<br>+-----+     +-----+<br>               |<br>            +-----+<br>            |  C  |<br>            +-----+</pre></div></div></div></div><p>For long stretches, this may be too tedious.&nbsp; You can start a code section by placing <code>(start code)</code> or just <code>(code)</code> alone on a line.&nbsp; You end it with either <code>(end code)</code> or just <code>(end)</code>.&nbsp; You can&rsquo;t put any other content on these lines.</p><pre class=Example>(start code)
+
+if (x == 0) {
+   DoSomething();
+}
+
+return x;
+
+(end)
+</pre><div class=NDContent><div class=CFunction><div class=CTopic><div class=CBody><pre class=CCode>if (x == 0) {<br>   DoSomething();<br>}<br><br>return x;</pre></div></div></div></div><p>You can also use <code>example</code>, <code>diagram</code>, or <code>table</code> instead of <code>code</code>.&nbsp; Just use whatever&rsquo;s appropriate.&nbsp; Always flexible, it will accept <code>begin</code> for <code>start</code> and it will accept <code>finish</code> or <code>done</code> for <code>end</code> so you don&rsquo;t have to remember the exact word.</p><a name="Images"></a><div class="SubTopic">Images</div><p>You can include images in your documentation by writing &ldquo;<code>(see filename)</code>&rdquo;.&nbsp; If you put it alone on a line it will be embedded in place.</p><pre class=Example>This is the first paragraph.
+
+(see logo.gif)
+
+This is the second paragraph.
+</pre><div class=NDContent><div class=CFunction><div class=CTopic><div class=CBody><p class=CParagraph>This is the first paragraph.</p><img src="../images/logo.gif" width="268" height="61"><p class=CParagraph>This is the second paragraph.</p></div></div></div></div><p>If it&rsquo;s not alone on a line the image will appear after the end of the paragraph, the text will become a link to it, and the file name will be used as a caption.</p><pre class=Example>This is the first paragraph (see logo.gif)  This is
+more of the first paragraph.
+</pre><div class=NDContent><div class=CFunction><div class=CTopic><div class=CBody><p class=CParagraph>This is the first paragraph <a href="#Image1" class=CImageLink>(see logo)</a>&nbsp; This is more of the first paragraph.</p><div class=CImage><a name="Image1"></a><div class=CImageCaption>logo</div><img src="../images/logo.gif" width="268" height="61"></div></div></div></div></div><p>The image file names are relative to the source file the comment appears in or any directories specified with the <a href="../running.html#CommandLine"><code>-img</code> command line option</a>.&nbsp; You can use relative paths like <code>(see images/logo.gif)</code> and <code>(see ../images/logo.gif)</code>, but you cannot use absolute paths, URLs, or specify a file that&rsquo;s not in a folder included by <a href="../running.html#CommandLine"><code>-i</code></a> or <a href="../running.html#CommandLine"><code>-img</code></a>.</p><p>Natural Docs supports gif, jpg, jpeg, png, and bmp files.</p></div><div class=Topic><a name="PageTitles"></a><div class=TopicTitle>Page Titles</div><p>Natural Docs automatically determines the page title as follows:</p><ul><li>If there&rsquo;s only one topic in the file, that topic&rsquo;s title becomes the page title.</li><li>Otherwise, if the first topic in the file is a class, section, or file, that topic&rsquo;s title becomes the page title.</li><li>Otherwise, the file name becomes the page title.</li><p></ul></p><p>This should be enough for most people.&nbsp; However, if you don&rsquo;t like the page title Natural Docs has chosen for you, add a &ldquo;<code>Title: [name]</code>&rdquo; comment to the top of the file to override it.&nbsp; <code>Title</code> is a synonym of Section, so that will satisfy the second rule and make it the page title.</p></div><div class=Topic><a name="Summaries"></a><div class=TopicTitle>Summaries</div><p>Summaries are automatically generated for every file, class, and section.&nbsp; You don&rsquo;t have to do anything special to get them.</p><p>There are two things you may want to keep in mind when documenting your code so that the summaries are nicer.&nbsp; The first is that they use the first sentence in the topic as the description, so long as it&rsquo;s plain text and not something like a bullet list.&nbsp; It will also appear in the tooltip whenever that topic is linked to.</p><p>The second is that you may want to manually add group topics to divide long lists and make the summaries easier to navigate.&nbsp; Natural Docs will automatically group them by type if you do not, but sometimes you want to be more specific.&nbsp; You don&rsquo;t need to provide a description, just adding a &ldquo;<code>Group: [name]</code>&rdquo; comment is sufficient.&nbsp; Note that once you manually add a group automatic grouping is completely turned off for that class.</p><p>Here&rsquo;s an example summary.&nbsp; Note that as before, when you hover over a link, you&rsquo;ll get the prototype and summary line as a tooltip.</p><div class=NDSummary><div class=Summary><div class=STitle>Summary</div><div class=SBorder><table border=0 cellspacing=0 cellpadding=0 class=STable><tr><td class=SEntrySize><div class=SMain><div class=SEntry><a href="#Example_Class" >Example Class</a></div></div></td><td class=SDescriptionSize><div class=SMain><div class=SDescription>A example class that does arithmetic with functions for people scared of operators.</div></div></td></tr><tr><td><div class=SGroup><div class=SEntry><div class=SIndent1><a href="#Example_Class.Arithmetic_Functions" >Arithmetic Functions</a></div></div></div></td><td><div class=SGroup><div class=SDescription><div class=SIndent1></div></div></div></td></tr><tr class=SMarked><td><div class=SFunction><div class=SEntry><div class=SIndent2><a href="#Example_Class.Add" id=link1 onMouseOver="ShowTip(event, 'ttAdd', 'link1')" onMouseOut="HideTip('ttAdd')">Add</a></div></div></div></td><td><div class=SFunction><div class=SDescription><div class=SIndent2>Adds two integers.</div></div></div></td></tr><tr><td><div class=SFunction><div class=SEntry><div class=SIndent2><a href="#Example_Class.Subtract" id=link2 onMouseOver="ShowTip(event, 'ttSubtract', 'link2')" onMouseOut="HideTip('ttSubtract')">Subtract</a></div></div></div></td><td><div class=SFunction><div class=SDescription><div class=SIndent2>Subtracts two integers.</div></div></div></td></tr><tr class=SMarked><td><div class=SFunction><div class=SEntry><div class=SIndent2><a href="#Example_Class.Multiply" id=link3 onMouseOver="ShowTip(event, 'ttMultiply', 'link3')" onMouseOut="HideTip('ttMultiply')">Multiply</a></div></div></div></td><td><div class=SFunction><div class=SDescription><div class=SIndent2>Multiplies two integers.</div></div></div></td></tr><tr><td><div class=SFunction><div class=SEntry><div class=SIndent2><a href="#Example_Class.Divide" id=link4 onMouseOver="ShowTip(event, 'ttDivide', 'link4')" onMouseOut="HideTip('ttDivide')">Divide</a></div></div></div></td><td><div class=SFunction><div class=SDescription><div class=SIndent2>Divides two integers.</div></div></div></td></tr><tr><td><div class=SGroup><div class=SEntry><div class=SIndent1><a href="#Example_Class.Comparison_Functions" >Comparison Functions</a></div></div></div></td><td><div class=SGroup><div class=SDescription><div class=SIndent1></div></div></div></td></tr><tr class=SMarked><td><div class=SFunction><div class=SEntry><div class=SIndent2><a href="#Example_Class.IsEqual" id=link5 onMouseOver="ShowTip(event, 'ttIsEqual', 'link5')" onMouseOut="HideTip('ttIsEqual')">IsEqual</a></div></div></div></td><td><div class=SFunction><div class=SDescription><div class=SIndent2>Returns whether two integers are equal.</div></div></div></td></tr></table></div></div></div></div><div class=Topic><a name="JavadocCompatibility"></a><div class=TopicTitle>Javadoc Compatibility</div><p>If you have <a href="../languages.html">full language support</a> Natural Docs will also extract documentation from actual Javadoc comments, not just Natural Docs comments written with the Javadoc comment symbols.&nbsp; This provides you with a good method of migrating to Natural Docs as you don&rsquo;t have to convert all of your existing documentation.</p><pre class=Example>/**
+ * Multiplies two integers.
+ *
+ * @param x The first integer.
+ * @param y The second integer.
+ * @return The two integers multiplied together.
+ * @see Divide
+ */
+</pre><div class=NDContent><div class=CFunction><div class=CTopic><h3 class=CTitle><a name="Example_Class.Multiply"></a>Multiply</h3><div class=CBody><table border=0 cellspacing=0 cellpadding=0 class=Prototype><tr><td><table border=0 cellspacing=0 cellpadding=0><tr><td class=PBeforeParameters>int Multiply (</td><td class=PType>int&nbsp;</td><td class=PParameter>x,</td><td></td></tr><tr><td></td><td class=PType>int&nbsp;</td><td class=PParameter>y</td><td class=PAfterParameters>)</td></tr></table></td></tr></table><p class=CParagraph>Multiplies two integers.</p><h4 class=CHeading>Parameters</h4><table border=0 cellspacing=0 cellpadding=0 class=CDescriptionList><tr><td class=CDLEntry>x</td><td class=CDLDescription>The first integer.</td></tr><tr><td class=CDLEntry>y</td><td class=CDLDescription>The second integer.</td></tr></table><h4 class=CHeading>Returns</h4><p class=CParagraph>The two integers multiplied together.</p><h4 class=CHeading>See Also</h4><p class=CParagraph><a href="#Example_Class.Divide" class=LFunction id=link116 onMouseOver="ShowTip(event, 'ttDivide', 'link116')" onMouseOut="HideTip('ttDivide')">Divide</a></p></div></div></div></div><p>While most Javadoc tags and simple HTML formatting are supported, Natural Docs is not a full Javadoc parser and may not support some advanced tags and HTML.&nbsp; See <a href="../documentation/html/files/Modules/NaturalDocs/Parser/JavaDoc-pm.html">this page</a> for a list of what&rsquo;s supported and what isn&rsquo;t.</p><p>A source file can contain both Natural Docs and Javadoc comments.&nbsp; However, you cannot mix the two within a single comment.&nbsp; For example, you can&rsquo;t use asterisks for bold in a <code>@param</code> line.&nbsp; If a comment is written with the Javadoc comment symbols (repeat the last symbol of the first line once) doesn&rsquo;t have a topic line (like &ldquo;<code>Function: Multiply</code>&rdquo;) and uses Javadoc tags (like <code>@param</code>) the comment is treated as Javadoc.&nbsp; If any of those conditions aren&rsquo;t true it&rsquo;s treated as a Natural Docs comment.</p></div></td></tr><tr><td></td><td class=SideMenuBottom><table width=100% border=0 cellspacing=0 cellpadding=0><tr><td class=SideMenuBottomLeft><img src="../images/menu/bottomleft.png" width=18 height=19></td><td class=SideMenuBottomRight><img src="../images/menu/bottomright.png" width=18 height=19></td></tr></table></td><td class=BodyBottom>Copyright &copy; 2003-2008 Greg Valure</td></tr></table><script language=JavaScript><!--
+ClosingBrowserTags();// --></script></body></html>
\ No newline at end of file

Added: trunk/build/NaturalDocs-1.4/Help/documenting/walkthrough.html
===================================================================
--- trunk/build/NaturalDocs-1.4/Help/documenting/walkthrough.html	                        (rev 0)
+++ trunk/build/NaturalDocs-1.4/Help/documenting/walkthrough.html	2009-07-01 15:30:00 UTC (rev 4275)
@@ -0,0 +1,180 @@
+
+
+<html><head><title>Documenting Your Code - Walkthrough - Natural Docs</title><link rel=stylesheet type="text/css" href="../styles.css"><link rel=stylesheet type="text/css" href="../examples.css"><script language=JavaScript src="../javascript/PNGHandling.js"></script><script language=JavaScript src="../javascript/BrowserStyles.js"></script><script language=JavaScript src="../example/NaturalDocs.js"></script></head><body marginwidth=0 marginheight=0 leftmargin=0 topmargin=0><script language=JavaScript><!--
+OpeningBrowserTags();// --></script>
+
+<!-- saved from url=(0026)http://www.naturaldocs.org -->
+
+<table width=100% border=0 cellspacing=0 cellpadding=0><tr><td colspan=3 class=Header><table width=100% border=0 cellspacing=0 cellpadding=0><tr><td><img src="../images/header/leftside.png" width=30 height=75><a href="../index.html"><img src="../images/header/logo.png" width=524 height=75 alt="Natural Docs"></a></td><td align=right><img src="../images/header/rightside.png" width=30 height=75></td></tr></table></td></tr><tr><td><img src="../images/header/overleftmargin.png" width=10 height=6></td><td class=SideMenuTop><img src="../images/header/overmenu.png" width=14 height=6></td><td class=BodyTop><img src="../images/header/overbody.png" width=24 height=6></td></tr><tr><td></td><td class=SideMenu nowrap><div class=SideMenuSection><div class=SideMenuTitle><img src="../images/menu/about.png" width=52 height=13 alt="About"></div><div class=SideMenuBody><a href="../languages.html" class=SideMenuEntry>Language Support</a><a href="../output.html" class=SideMenuEntry>Output Formats</a></div></div><div class=SideMenuSection><div class=SideMenuTitle><img src="../images/menu/using.png" width=45 height=13 alt="Using"></div><div class=SideMenuBody><a href="../documenting.html" class=SideMenuEntry id=SelectedSideMenuEntry>Documenting<br>Your Code</a><a href="../keywords.html" class=SideMenuEntry>Keywords</a><a href="../running.html" class=SideMenuEntry>Running</a><a href="../troubleshooting.html" class=SideMenuEntry>Troubleshooting</a></div></div><div class=SideMenuSection><div class=SideMenuTitle><img src="../images/menu/customizing.png" width=96 height=13 alt="Customizing"></div><div class=SideMenuBody><a href="../menu.html" class=SideMenuEntry>Organizing the Menu</a><a href="../styles.html" class=SideMenuEntry>CSS Styles</a><a href="../customizingtopics.html" class=SideMenuEntry>Topics and Keywords</a><a href="../customizinglanguages.html" class=SideMenuEntry>Languages, Indexes,<br>and Prototypes</a></div></div><div class=SideMenuSection><div class=SideMenuTitle><img src="../images/menu/community.png" width=86 height=13 alt="Community"></div><div class=SideMenuBody><a href="http://www.naturaldocs.org/" class=SideMenuEntry>Web Site</a><a href="http://www.naturaldocs.org/mailinglist.html" class=SideMenuEntry>Mailing Lists</a><a href="http://www.naturaldocs.org/messageboards.html" class=SideMenuEntry>Message Boards</a><a href="http://www.naturaldocs.org/bugs.html" class=SideMenuEntry>Bugs and<br>Feature Requests</a></div></div></td><td class=Body width=100%><div class=PageTitle>Documenting Your Code</div><div class=TOC><a href="#OurFirstFunction">Our First Function</a> &middot; <a href="#ClassesAndScope">Classes and Scope</a> &middot; <a href="#MoreFormatting">More Formatting</a><br><a href="#MoreOnLinking">More on Linking</a> &middot; <a href="#ExtraDocumentation">Extra Documentation</a> &middot; <a href="#AbbreviatedSyntax">Abbreviated Syntax</a></div><div class=Topic><a name="OurFirstFunction"></a><div class=TopicTitle>Our First Function</div><p>So you downloaded Natural Docs, you <a href="../running.html">figured out the command line</a>, and now it&rsquo;s time to start documenting your code.&nbsp; Natural Docs tries to make this very straightforward and painless, so let&rsquo;s just dive right in:</p><pre class=Example>/*
+   Function: Multiply
+   Multiplies two integers and returns the result.
+*/
+int Multiply (int x, int y)
+   {  return x * y;  };
+</pre><p>That&rsquo;s all you need.&nbsp; Run Natural Docs and here&rsquo;s what appears in your output:</p><div class=NDContent><div class=CFunction><div class=CTopic><h3 class=CTitle><a name="Multiply"></a>Multiply</h3><div class=CBody><table border=0 cellspacing=0 cellpadding=0 class=Prototype><tr><td><table border=0 cellspacing=0 cellpadding=0><tr><td class=PBeforeParameters>int Multiply (</td><td class=PType>int&nbsp;</td><td class=PParameter>x,</td><td></td></tr><tr><td></td><td class=PType>int&nbsp;</td><td class=PParameter>y</td><td class=PAfterParameters>)</td></tr></table></td></tr></table><p class=CParagraph>Multiplies two integers and returns the result.</p></div></div></div></div><p>Okay, so that&rsquo;s all you need, but probably not all you want.&nbsp; After all, you&rsquo;ve got some real functions to document, not little one-liners.&nbsp; Here&rsquo;s something more elaborate:</p><pre class=Example>/*
+   Function: Multiply
+
+   Multiplies two integers.
+
+   Parameters:
+
+      x - The first integer.
+      y - The second integer.
+
+   Returns:
+
+      The two integers multiplied together.
+
+   See Also:
+
+      &lt;Divide&gt;
+*/
+int Multiply (int x, int y)
+   {  return x * y;  };
+</pre><div class=NDContent><div class=CFunction><div class=CTopic><h3 class=CTitle><a name="Example_Class.Multiply"></a>Multiply</h3><div class=CBody><table border=0 cellspacing=0 cellpadding=0 class=Prototype><tr><td><table border=0 cellspacing=0 cellpadding=0><tr><td class=PBeforeParameters>int Multiply (</td><td class=PType>int&nbsp;</td><td class=PParameter>x,</td><td></td></tr><tr><td></td><td class=PType>int&nbsp;</td><td class=PParameter>y</td><td class=PAfterParameters>)</td></tr></table></td></tr></table><p class=CParagraph>Multiplies two integers.</p><h4 class=CHeading>Parameters</h4><table border=0 cellspacing=0 cellpadding=0 class=CDescriptionList><tr><td class=CDLEntry>x</td><td class=CDLDescription>The first integer.</td></tr><tr><td class=CDLEntry>y</td><td class=CDLDescription>The second integer.</td></tr></table><h4 class=CHeading>Returns</h4><p class=CParagraph>The two integers multiplied together.</p><h4 class=CHeading>See Also</h4><p class=CParagraph><a href="#Example_Class.Divide" class=LFunction id=link116 onMouseOver="ShowTip(event, 'ttDivide', 'link116')" onMouseOut="HideTip('ttDivide')">Divide</a></p></div></div></div></div><div class=CToolTip id="ttAdd"><div class=CFunction><table border=0 cellspacing=0 cellpadding=0 class=Prototype><tr><td><table border=0 cellspacing=0 cellpadding=0><tr><td class=PBeforeParameters>int Add (</td><td class=PType>int&nbsp;</td><td class=PParameter>x,</td><td></td></tr><tr><td></td><td class=PType>int&nbsp;</td><td class=PParameter>y</td><td class=PAfterParameters>)</td></tr></table></td></tr></table>Adds two integers.</div></div><div class=CToolTip id="ttSubtract"><div class=CFunction><table border=0 cellspacing=0 cellpadding=0 class=Prototype><tr><td><table border=0 cellspacing=0 cellpadding=0><tr><td class=PBeforeParameters>int Subtract (</td><td class=PType>int&nbsp;</td><td class=PParameter>x,</td><td></td></tr><tr><td></td><td class=PType>int&nbsp;</td><td class=PParameter>y</td><td class=PAfterParameters>)</td></tr></table></td></tr></table>Subtracts two integers.</div></div><div class=CToolTip id="ttMultiply"><div class=CFunction><table border=0 cellspacing=0 cellpadding=0 class=Prototype><tr><td><table border=0 cellspacing=0 cellpadding=0><tr><td class=PBeforeParameters>int Multiply (</td><td class=PType>int&nbsp;</td><td class=PParameter>x,</td><td></td></tr><tr><td></td><td class=PType>int&nbsp;</td><td class=PParameter>y</td><td class=PAfterParameters>)</td></tr></table></td></tr></table>Multiplies two integers.</div></div><div class=CToolTip id="ttDivide"><div class=CFunction><table border=0 cellspacing=0 cellpadding=0 class=Prototype><tr><td><table border=0 cellspacing=0 cellpadding=0><tr><td class=PBeforeParameters>int Divide (</td><td class=PType>int&nbsp;</td><td class=PParameter>x,</td><td></td></tr><tr><td></td><td class=PType>int&nbsp;</td><td class=PParameter>y</td><td class=PAfterParameters>)</td></tr></table></td></tr></table>Divides two integers.</div></div><div class=CToolTip id="ttIsEqual"><div class=CFunction><table border=0 cellspacing=0 cellpadding=0 class=Prototype><tr><td><table border=0 cellspacing=0 cellpadding=0><tr><td class=PBeforeParameters>bool IsEqual (</td><td class=PType>int&nbsp;</td><td class=PParameter>x,</td><td></td></tr><tr><td></td><td class=PType>int&nbsp;</td><td class=PParameter>y</td><td class=PAfterParameters>)</td></tr></table></td></tr></table>Returns whether two integers are equal.</div></div><p>Still not too scary, huh?&nbsp; Notice the comments are just as readable as the output.&nbsp; No tags littered about, and the structure is very natural.&nbsp; You probably get it just by looking at it, but let&rsquo;s go through the details anyway.</p><pre class=Example>Function: Multiply
+</pre><p>Every one of these comments you write (called <i>topics</i>) are going to start with a <i>topic line</i> in the format <code>&ldquo;keyword: title&rdquo;</code>.&nbsp; There are <a href="../keywords.html">a lot of keywords</a>, but they&rsquo;re exactly what you&rsquo;d expect: Function, Class, Variable, etc.&nbsp; There are also a lot of synonyms so instead of Function you could use Func, Procedure, Proc, Method, Constructor, etc.&nbsp; It&rsquo;s designed so you can just use whatever it is you&rsquo;re describing without memorizing anything.&nbsp; You can glance over <a href="../keywords.html">the keyword list</a> but you shouldn&rsquo;t have to consult it very often.</p></p><p>The other part of the topic line is the title.&nbsp; It should match whatever it is you&rsquo;re documenting, in this case the function name Multiply.&nbsp; Natural Docs is case sensitive even if your programming language isn&rsquo;t, so make sure you match it closely or you might not get the prototype in your output, which is the little gray box.&nbsp; You don&rsquo;t need to include the parameters in the title.&nbsp; In fact, it&rsquo;s better if you don&rsquo;t.</p></p><pre class=Example>Parameters:
+
+Returns:
+
+See Also:
+</pre><p>You can also define <a href="reference.html#Headings">headings</a> by skipping a line and ending the text with a colon.&nbsp; If you&rsquo;re used to other documentation systems you may think there&rsquo;s only a handful of headings to choose from, but any text formatted this way will become one.&nbsp; If you want a heading called Dependencies you can go right ahead and add it.</p><pre class=Example>x - The first integer.
+y - The second integer.
+</pre><p>This is what&rsquo;s called a <a href="reference.html#DefinitionLists">definition list.</a>&nbsp; You can use more than one line to finish the definition, as it won&rsquo;t stop until you skip a line.</p><pre class=Example>x - The first integer.
+y - The second integer with a long description.
+    This is still part of the description.
+
+This is a new paragraph because we skipped a line.
+</pre><p>Indentation doesn&rsquo;t matter either, so even if the second description line wasn&rsquo;t indented to match the first, it would still be considered part of it.</p><pre class=Example>&lt;Divide&gt;
+</pre><p>This is how we link in Natural Docs, with angle brackets.&nbsp; There will be a lot more to say about this later, but for now I&rsquo;ll just show you something cool.&nbsp; Hover over it in the output below:</p><div class=NDContent><div class=CTopic><div class=CBody><p class=CParagraph><a href="#Example_Class.Divide" class=LFunction id=link116 onMouseOver="ShowTip(event, 'ttDivide', 'link216')" onMouseOut="HideTip('ttDivide')">Divide</a></p></div></div></div><p>You get that <i>everywhere</i> in your generated documentation.</p></div><div class=Topic><a name="ClassesAndScope"></a><div class=TopicTitle>Classes and Scope</div><p>So that&rsquo;s good for our one function of questionable usefulness, but what if we have a whole class of questionable usefulness?&nbsp; We can document the class and it&rsquo;s members the same way we documented the individual function, with a Natural Docs comment right above each element.&nbsp; We&rsquo;ll go back to short descriptions to keep the example manageable.</p><pre class=Example>/*
+   Class: Counter
+   A class that manages an incrementing counter.
+*/
+class Counter
+   {
+   public:
+
+      /*
+         Constructor: Counter
+         Initializes the object.
+      */
+      Counter()
+         {  value = 0;  };
+
+      /*
+         Function: Value
+         Returns the value of the counter.
+      */
+      int Value()
+         {  return value;  };
+
+      /*
+         Function: Increment
+         Adds one to the counter.
+      */
+      void Increment()
+         {  value++;  };
+
+   protected:
+
+      /*
+         Variable: value
+         The counter's value.
+      */
+      int value;
+   };
+</pre><p>Everything&rsquo;s the same, we just substituted Class and Variable for the Function keyword when it was appropriate.&nbsp; We also used Constructor, but we could have just as easily used Function there too.&nbsp; They&rsquo;re both keywords for the same thing so it doesn&rsquo;t matter.</p><a name="Scope"></a><div class="SubTopic">Scope</div><p>Like the source code itself, Natural Docs topics have <a href="reference.html#Scope">scope.</a>&nbsp; Value and Increment are seen as part of class Counter, just like they are in the code.&nbsp; Why is this important?&nbsp; Linking.&nbsp; Linking from one topic to another has similar rules to how one function can call another.&nbsp; Since Value is in the same class as Increment, it&rsquo;s topic can link to it with just <code>&lt;Increment&gt;</code>.&nbsp; However, linking to Increment from a different class would require <code>&lt;Counter.Increment&gt;</code> instead.&nbsp; You can actually use any of the three most common class/member notations: <code>&lt;Counter.Increment&gt;</code>, <code>&lt;Counter::Increment&gt;</code>, and <code>&lt;Counter-&gt;Increment&gt;</code>.</p><p>If your programming language has <a href="../languages.html">full language support</a>, the scope is determined by the code and applied automatically.&nbsp; However, if you only have <a href="../languages.html">basic language support</a> it follows these rules:</p><ul><li>Any topic that appears under a Class topic (or anything that says <a href="../keywords.html">Starts Scope</a>) is part of that class.</li><li>Any topic that appears under a Section topic (or anything that says <a href="../keywords.html">Ends Scope</a>) is global again.</li><li>Any File topic (or anything that says <a href="../keywords.html">Always Global</a>) is global no matter what and doesn&rsquo;t affect any other topics.</li><p></ul></p><p>Chances are you would have written the same thing even if you didn&rsquo;t know this and it would have just worked.&nbsp; You usually won&rsquo;t need to think about them at all.&nbsp; However, it&rsquo;s still good to be aware of them in case something doesn&rsquo;t behave the way you expected it to.</p><p>You actually know enough to go start documenting now.&nbsp; I know Mr. ScrollBar says there&rsquo;s more on this page you can learn, but if you want to skip out early, you can.&nbsp; Really.&nbsp; I don&rsquo;t mind.</p></div><div class=Topic><a name="MoreFormatting"></a><div class=TopicTitle>More Formatting</div><p>Okay then.&nbsp; On we go.</p><a name="ParagraphsBoldAndUnderline"></a><div class="SubTopic">Paragraphs, Bold, and Underline</div><p>The syntax for these three is exactly what you would expect it to be.</p><pre class=Example>*Bold text*
+
+_Underlined text_
+
+Paragraphs are broken by skipping lines.  So the two
+lines above each have their own paragraph, but these
+three lines are all part of the same one.
+</pre><div class=NDContent><div class=CBody><div class=CFunction><div class=CTopic><p><b>Bold text</b></p><p><u>Underlined text</u></p><p>Paragraphs are broken by skipping lines.&nbsp; So the two lines above each have their own paragraph, but these three lines are all part of the same one.</p></div></div></div></div><p>When underlining multiple words, you can use an underscore for each space or only put them at the edges like we did above.&nbsp; Both ways will work.</p><a name="BulletLists"></a><div class="SubTopic">Bullet Lists</div><p>You can add <a href="reference.html#BulletLists">bullet lists</a> by starting a line with a dash, an asterisk, an o, or a plus.&nbsp; Like definition lists, bullets can span multiple lines and indentation doesn&rsquo;t matter.&nbsp; To end a bullet you have to skip a line before doing something else.</p><pre class=Example>- Bullet one.
+- Bullet two.
+  Bullet two continued.
+- Bullet three.
+
+Some text after the bullet list.
+</pre><div class=NDContent><div class=CFunction><div class=CTopic><div class=CBody><ul class=CBulletList><li>Bullet one.</li><li>Bullet two.&nbsp; Bullet two continued.</li><li>Bullet three.</li></ul><p class=CParagraph>Some text after the bullet list.</p></div></div></div></div><a name="CodeAndTextDiagrams"></a><div class="SubTopic">Code and Text Diagrams</div><p>You can add <a href="reference.html#CodeAndTextDiagrams">example code or text diagrams</a> by starting each line with <code>&gt;</code>, <code>|</code>, or <code>:</code>.</p><pre class=Example>&gt; a = b + c;
+&gt; b++;
+</pre><div class=NDContent><div class=CFunction><div class=CTopic><div class=CBody><pre class=CCode>a = b + c;<br>b++;</pre></div></div></div></div><p>If you have a long stretch, you can use <code>(start code)</code> and <code>(end)</code> instead.</p><pre class=Example>(start code)
+
+if (x == 0) {
+   DoSomething();
+}
+
+return x;
+
+(end)
+</pre><div class=NDContent><div class=CFunction><div class=CTopic><div class=CBody><pre class=CCode>if (x == 0) {<br>   DoSomething();<br>}<br><br>return x;</pre></div></div></div></div><p>You can also use <code>example</code>, <code>diagram</code>, or <code>table</code> instead of <code>code</code>.&nbsp; Just use whatever&rsquo;s appropriate.</p><p>As I mentioned before, we don&rsquo;t want you to worry about memorizing minor details, so in that spirit it will also accept <code>begin</code> for <code>start</code> and <code>finish</code> or <code>done</code> for <code>end</code>.&nbsp; You can also write <code>(end code)</code> instead of just <code>(end)</code>.</p><a name="Images"></a><div class="SubTopic">Images</div><p>You can include images in your documentation by writing &ldquo;<code>(see filename)</code>&rdquo;.&nbsp; If you put it alone on a line it will be embedded in place, or if you put it in a paragraph it will appear after it using the file name as a caption.</p><pre class=Example>This is the first paragraph.
+
+(see logo.gif)
+
+This is the second paragraph (see logo.gif)  This
+is more of the second paragraph.
+</pre><div class=NDContent><div class=CFunction><div class=CTopic><div class=CBody><p class=CParagraph>This is the first paragraph.</p><img src="../images/logo.gif" width="268" height="61"><p class=CParagraph>This is the second paragraph <a href="#Image1" class=CImageLink>(see logo)</a>&nbsp; This is more of the second paragraph.</p><div class=CImage><a name="Image1"></a><div class=CImageCaption>logo</div><img src="../images/logo.gif" width="268" height="61"></div></div></div></div></div><p>The image file names are relative to the source file the comment appears in, so if your file is C:\Project\SourceFile.cpp and your image is C:\Project\Images\Logo.gif, you would write <code>(see Images/Logo.gif)</code>.&nbsp; However, you can also specify image directories in <a href="../running.html#CommandLine">the command line with <code>-img</code></a>, so if all your source files are in C:\Project\Source and all your images are in C:\Project\Images, you can put &ldquo;<code>-img C:\Project\Images</code>&rdquo; on the command line and just use <code>(see logo.gif)</code> again.</p></div><div class=Topic><a name="MoreOnLinking"></a><div class=TopicTitle>More on Linking</div><p>Yes, there&rsquo;s still more to linking.&nbsp; You can link to URLs and e-mail addresses, but in this case the angle brackets are optional.</p><pre class=Example>Visit &lt;http://www.website.com&gt; or send messages to
+email at address.com.
+</pre><div class=NDContent><div class=CFunction><div class=CTopic><div class=CBody><p class=CParagraph>Visit <a href="#" onClick="return false;" class=LURL>http://www.website.com</a> or send messages to <a href="#" onclick="location.href='mai' + 'lto:' + 'em' + 'ail' + '@' + 'addre' + 'ss.com'; return false;" class="LEMail">em<span style="display: none;">.nosp at m.</span>ail<span>@</span>addre<span style="display: none;">.nosp at m.</span>ss.com</a>.</p></div></div></div></div><p>E-mail addresses are protected from spam crawlers.&nbsp; They look and act like regular links (try it above) but you can see the actual HTML that&rsquo;s generated for them <a href="reference.html#URLsAndEMail">here</a>.</p><p>As for regular links, to help them fit into sentences easily you can actually include plurals and possessives inside the angle brackets.&nbsp; In other words, you don&rsquo;t have to use awkward syntax like <code>&lt;Object&gt;s</code>, although that&rsquo;s supported as well.&nbsp; You can simply write <code>&lt;Objects&gt;</code> and it will link to the symbol <code>Object</code> just fine.&nbsp; It can handle any plural and/or possessive form you can throw at it.&nbsp; I&rsquo;m not kidding: <code>Foxes</code>, <code>Fox&rsquo;s</code>, <code>Foxes&rsquo;</code>, <code>Children</code>, <code>Mice</code>, <code>Alumni</code>, <code>Indices</code>, <code>Amoebae</code>, <code>Teeth</code>, just try to trip it up.</p></div><div class=Topic><a name="ExtraDocumentation"></a><div class=TopicTitle>Extra Documentation</div><p>Sometimes you want to include documentation that doesn&rsquo;t correspond directly to a code element.&nbsp; Maybe you want to include license information or architecture notes.&nbsp; There are two ways to do this.</p><a name="FreestandingTopics"></a><div class="SubTopic">Freestanding Topics</div><p>Just because most of the things you write will directly correspond to an element of your source code doesn&rsquo;t mean they have to.&nbsp; You can pick any of <a href="../keywords.html">the available keywords</a> and create a freestanding comment with it.&nbsp; For example:</p><pre class=Example>/*
+   Class: Counter
+   A class that manages an incrementing counter.
+*/
+class Counter
+   {
+   public:
+
+      /*
+         About: License
+         This file is licensed under the GPL.
+      */
+
+      /*
+         Constructor: Counter
+         Initializes the object.
+      */
+      Counter()
+         {  value = 0;  };
+   ...
+</pre><p>The extra license topic will be added to the output just like the functions.</p><div class=NDContent><div class=CFunction><div class=CTopic><h3 class=CTitle>License</h3><div class=CBody><p class=CParagraph>This file is licensed under the GPL.</p></div></div></div></div><p>Remember that because of <a href="#Scope">scope</a>, the License topic will actually be considered part of Counter the way it&rsquo;s listed above.&nbsp; You&rsquo;d link to it from outside Counter with <code>&lt;Counter.License&gt;</code>.&nbsp; That idea may take some getting used to, but if an extra topic only applies to one class that&rsquo;s actually the most appropriate way to do it.&nbsp; In this case it&rsquo;s a license, so if it applies to the entire project instead you could put the comment above the class to make it global, just like moving a function there would.</p><a name="TextFiles"></a><div class="SubTopic">Text Files</div><p>You can also add additional documentation with text files.&nbsp; If you put a file with a .txt extension in your source tree and start it with a topic line, it&rsquo;s contents will be treated the same as if it were in a comment in your source code.&nbsp; That means you can define multiple topics within it, you can link between them and topics in your source code, and you can use all available formatting options.</p><pre class=Example>Title: License
+
+This file is licensed under the GPL.
+
+I can link to &lt;Counter&gt; and &lt;Counter.Increment&gt;, and
+the documentation in that class can even link back
+with &lt;License&gt;.
+
+
+About: Second Topic
+
+I can create a *second* topic in here too, complete
+with formatting.
+</pre><p>The thing some people forget though is that you <b>must</b> start it with a topic line, like &ldquo;<code>Title: License</code>&rdquo; above.&nbsp; This is how Natural Docs tells it apart from regular text files.</p></div><div class=Topic><a name="AbbreviatedSyntax"></a><div class=TopicTitle>Abbreviated Syntax</div><p>Here&rsquo;s another useful thing you may want to know about.&nbsp; Suppose you have a lot of little things to document, like constants.&nbsp; Writing a separate topic for each one can be very tedious, no matter how much you compress it:</p><pre class=Example>// Constant: COUNTER_NORMAL
+// Causes the counter to increment normally.
+#define COUNTER_NORMAL 0
+
+// Constant: COUNTER_ODD
+// Causes the counter to only increment in odd numbers.
+#define COUNTER_ODD 1
+
+// Constant: COUNTER_EVEN
+// Causes the counter to only increment in even numbers.
+#define COUNTER_EVEN 2
+</pre><p>One thing you may have noticed in the <a href="../keywords.html">keyword list</a> is that they almost all have plural forms.&nbsp; These are used to create what are called <a href="reference.html#ListTopics">list topics.</a>&nbsp; You define a topic using a plural keyword, and then anything appearing in a <a href="reference.html#DefinitionLists">definition list</a> within it creates a linkable symbol as if they each had their own topic.&nbsp; For example:</p><pre class=Example>/*
+   Constants: Counter Modes
+
+   COUNTER_NORMAL - Causes the counter to increment normally.
+   COUNTER_ODD    - Causes the counter to only increment in odd numbers.
+   COUNTER_EVEN   - Causes the counter to only increment in even numbers.
+*/
+#define COUNTER_NORMAL 0
+#define COUNTER_ODD 1
+#define COUNTER_EVEN 2
+</pre><p>I would now be able to write <code>&lt;COUNTER_ODD&gt;</code> and have it work the same as it would with the first example.</p><p>Using the enum or enumeration keyword is special because it automatically behaves in a similar manner.&nbsp; This allows both the enum and its values to be documented in the same place.</p><pre class=Example>/*
+   Enum: CounterMode
+
+   NORMAL - Causes the counter to increment normally.
+   ODD    - Causes the counter to only increment in odd numbers.
+   EVEN   - Causes the counter to only increment in even numbers.
+*/
+enum CounterMode { NORMAL, ODD, EVEN };
+</pre><p>That&rsquo;s it, you&rsquo;re done with this walkthrough.&nbsp; You should know enough now to make very good use of Natural Docs.&nbsp; If you still want to know more, you can look in <a href="reference.html">the reference</a> for some of the smaller details we may have skipped over.&nbsp; Also, look at the Customizing pages on this web site for even more you can do.</p></div></td></tr><tr><td></td><td class=SideMenuBottom><table width=100% border=0 cellspacing=0 cellpadding=0><tr><td class=SideMenuBottomLeft><img src="../images/menu/bottomleft.png" width=18 height=19></td><td class=SideMenuBottomRight><img src="../images/menu/bottomright.png" width=18 height=19></td></tr></table></td><td class=BodyBottom>Copyright &copy; 2003-2008 Greg Valure</td></tr></table><script language=JavaScript><!--
+ClosingBrowserTags();// --></script></body></html>
\ No newline at end of file

Added: trunk/build/NaturalDocs-1.4/Help/documenting.html
===================================================================
--- trunk/build/NaturalDocs-1.4/Help/documenting.html	                        (rev 0)
+++ trunk/build/NaturalDocs-1.4/Help/documenting.html	2009-07-01 15:30:00 UTC (rev 4275)
@@ -0,0 +1,58 @@
+
+
+<html><head><title>Documenting Your Code - Natural Docs</title><link rel=stylesheet type="text/css" href="styles.css"><link rel=stylesheet type="text/css" href="examples.css"><style type="text/css"><!--
+
+
+    #ReferenceTable {
+        width: 100%;
+        }
+    #ReferenceTable #LeftSide {
+        padding-right: 4ex;
+        width: 40%;
+        }
+    #ReferenceTable #RightSide {
+        width: 60%;
+        }
+
+    #LeftSide a:link,
+    #LeftSide a:hover,
+    #LeftSide a:visited {
+        color: #000000;
+        }
+
+    #LeftSide .TopicTitle a:hover,
+    #LeftSide .TopicTitle a:active {
+        text-decoration: none;
+        }
+
+    #RightSide .Example {
+        margin-left: 0;
+        margin-right: 0;
+        }
+    
+--></style><script language=JavaScript src="javascript/PNGHandling.js"></script><script language=JavaScript src="javascript/BrowserStyles.js"></script></head><body marginwidth=0 marginheight=0 leftmargin=0 topmargin=0><script language=JavaScript><!--
+OpeningBrowserTags();// --></script>
+
+<!-- saved from url=(0026)http://www.naturaldocs.org -->
+
+<table width=100% border=0 cellspacing=0 cellpadding=0><tr><td colspan=3 class=Header><table width=100% border=0 cellspacing=0 cellpadding=0><tr><td><img src="images/header/leftside.png" width=30 height=75><a href="index.html"><img src="images/header/logo.png" width=524 height=75 alt="Natural Docs"></a></td><td align=right><img src="images/header/rightside.png" width=30 height=75></td></tr></table></td></tr><tr><td><img src="images/header/overleftmargin.png" width=10 height=6></td><td class=SideMenuTop><img src="images/header/overmenu.png" width=14 height=6></td><td class=BodyTop><img src="images/header/overbody.png" width=24 height=6></td></tr><tr><td></td><td class=SideMenu nowrap><div class=SideMenuSection><div class=SideMenuTitle><img src="images/menu/about.png" width=52 height=13 alt="About"></div><div class=SideMenuBody><a href="languages.html" class=SideMenuEntry>Language Support</a><a href="output.html" class=SideMenuEntry>Output Formats</a></div></div><div class=SideMenuSection><div class=SideMenuTitle><img src="images/menu/using.png" width=45 height=13 alt="Using"></div><div class=SideMenuBody><span class=SideMenuEntry id=SelectedSideMenuEntry>Documenting<br>Your Code</span><a href="keywords.html" class=SideMenuEntry>Keywords</a><a href="running.html" class=SideMenuEntry>Running</a><a href="troubleshooting.html" class=SideMenuEntry>Troubleshooting</a></div></div><div class=SideMenuSection><div class=SideMenuTitle><img src="images/menu/customizing.png" width=96 height=13 alt="Customizing"></div><div class=SideMenuBody><a href="menu.html" class=SideMenuEntry>Organizing the Menu</a><a href="styles.html" class=SideMenuEntry>CSS Styles</a><a href="customizingtopics.html" class=SideMenuEntry>Topics and Keywords</a><a href="customizinglanguages.html" class=SideMenuEntry>Languages, Indexes,<br>and Prototypes</a></div></div><div class=SideMenuSection><div class=SideMenuTitle><img src="images/menu/community.png" width=86 height=13 alt="Community"></div><div class=SideMenuBody><a href="http://www.naturaldocs.org/" class=SideMenuEntry>Web Site</a><a href="http://www.naturaldocs.org/mailinglist.html" class=SideMenuEntry>Mailing Lists</a><a href="http://www.naturaldocs.org/messageboards.html" class=SideMenuEntry>Message Boards</a><a href="http://www.naturaldocs.org/bugs.html" class=SideMenuEntry>Bugs and<br>Feature Requests</a></div></div></td><td class=Body width=100%><div class=PageTitle>Documenting Your Code</div><div class=Topic><table id=ReferenceTable border=0 cellspacing=0 cellpadding=0><tr><td id=LeftSide><div class=Topic><div class=TopicTitle><a href="documenting/walkthrough.html">Walkthrough</a></div><ul><li><a href="documenting/walkthrough.html#OurFirstFunction">Our First Function</a></i><li><a href="documenting/walkthrough.html#ClassesAndScope">Classes and Scope</a></i><li><a href="documenting/walkthrough.html#MoreFormatting">More Formatting</a></i><li><a href="documenting/walkthrough.html#MoreOnLinking">More on Linking</a></i><li><a href="documenting/walkthrough.html#ExtraDocumentation">Extra Documentation</a></i><li><a href="documenting/walkthrough.html#AbbreviatedSyntax">Abbreviated Syntax</a></i></ul></div><div class=Topic><div class=TopicTitle><a href="documenting/reference.html">Reference</a></div><ul><li><a href="documenting/reference.html#Comments">Comments</a></i><li><a href="documenting/reference.html#TextFiles">Text Files</a></i><li><a href="documenting/reference.html#KeywordsTopicsAndScope">Keywords, Topics, and Scope</a></i><li><a href="documenting/reference.html#Linking">Linking</a></i><li><a href="documenting/reference.html#FormattingAndLayout">Formatting and Layout</a></i><li><a href="documenting/reference.html#PageTitles">Page Titles</a></i><li><a href="documenting/reference.html#Summaries">Summaries</a></i><li><a href="documenting/reference.html#JavadocCompatibility">Javadoc Compatibility</a></i></ul></div></td><td id=RightSide><p>Here&rsquo;s a quick example of how to document your code for Natural Docs.&nbsp; If you&rsquo;re a new user, we have <a href="documenting/walkthrough.html">a walkthrough</a> to get you started.&nbsp; Otherwise, visit <a href="documenting/reference.html">the reference</a> for the full details.</p><pre class=Example>/*
+   Function: Multiply
+
+   Multiplies two integers.
+
+   Parameters:
+
+      x - The first integer.
+      y - The second integer.
+
+   Returns:
+
+      The two integers multiplied together.
+
+   See Also:
+
+      &lt;Divide&gt;
+*/
+int Multiply (int x, int y)
+   {  return x * y;  };</pre></td></tr></table></div></td></tr><tr><td></td><td class=SideMenuBottom><table width=100% border=0 cellspacing=0 cellpadding=0><tr><td class=SideMenuBottomLeft><img src="images/menu/bottomleft.png" width=18 height=19></td><td class=SideMenuBottomRight><img src="images/menu/bottomright.png" width=18 height=19></td></tr></table></td><td class=BodyBottom>Copyright &copy; 2003-2008 Greg Valure</td></tr></table><script language=JavaScript><!--
+ClosingBrowserTags();// --></script></body></html>
\ No newline at end of file

Added: trunk/build/NaturalDocs-1.4/Help/example/Default.css
===================================================================
--- trunk/build/NaturalDocs-1.4/Help/example/Default.css	                        (rev 0)
+++ trunk/build/NaturalDocs-1.4/Help/example/Default.css	2009-07-01 15:30:00 UTC (rev 4275)
@@ -0,0 +1,528 @@
+/*
+   IMPORTANT: If you're editing this file in the output directory of one of
+   your projects, your changes will be overwritten the next time you run
+   Natural Docs.  Instead, copy this file to your project directory, make your
+   changes, and you can use it with -s.  Even better would be to make a CSS
+   file in your project directory with only your changes, which you can then
+   use with -s [original style] [your changes].
+
+   On the other hand, if you're editing this file in the Natural Docs styles
+   directory, the changes will automatically be applied to all your projects
+   that use this style the next time Natural Docs is run on them.
+
+   This file is part of Natural Docs, which is Copyright © 2003-2004 Greg Valure
+   Natural Docs is licensed under the GPL
+*/
+
+body {
+    font-family: Verdana, Arial, sans-serif;
+    color: #000000;
+    margin: 0px; padding: 0px }
+
+body.UnframedPage {
+    background-color: #E8E8E8 }
+
+
+a:link,
+a:visited { color: #900000; text-decoration: none }
+a:hover { color: #900000; text-decoration: underline }
+a:active { color: #FF0000; text-decoration: underline }
+
+td {
+    vertical-align: top }
+
+/*
+    Comment out this line to use web-style paragraphs (blank line between
+    paragraphs, no indent) instead of print-style paragraphs (no blank line,
+    indented.)
+*/
+p {
+    text-indent: 5ex; margin: 0 }
+
+
+/*  Can't use something like display: none or it won't break.  */
+.HB {
+    font-size: 1px }
+
+
+
+
+body.FramedMenuPage,
+.MenuSection {
+    font-size: 9pt;
+    background-color: #E8E8E8;
+    padding: 10px 0 0 0 }
+
+.MenuSection {
+    width: 27ex }
+
+
+    .MTitle {
+        font-size: 16pt; font-weight: bold; font-variant: small-caps;
+        text-align: center;
+        padding: 5px 10px 15px 10px;
+        border-bottom: 1px dotted #000000;
+        margin-bottom: 15px }
+
+    .MSubTitle {
+        font-size: 9pt; font-weight: normal; font-variant: normal;
+        margin-top: 1ex; margin-bottom: 5px }
+
+
+    .MEntry a:link,
+    .MEntry a:hover,
+    .MEntry a:visited { color: #606060; margin-right: 0 }
+    .MEntry a:active { color: #A00000; margin-right: 0 }
+
+
+    .MGroup {
+        font-variant: small-caps; font-weight: bold;
+        margin: 1em 0 1em 10px }
+
+    /*  Konqueror just can't do margins.  */
+    .KHTML .MGroup {
+        margin-bottom: 0; padding-bottom: 1em }
+
+    .MGroupContent {
+        font-variant: normal; font-weight: normal }
+
+    .MGroup a:link,
+    .MGroup a:hover,
+    .MGroup a:visited { color: #545454; margin-right: 10px }
+    .MGroup a:active { color: #A00000; margin-right: 10px }
+
+
+    .MFile,
+    .MText,
+    .MLink,
+    .MIndex {
+        padding: 1px 17px 2px 10px;
+        margin: .25em 0 .25em 0 }
+
+    .MText {
+        font-size: 8pt; font-style: italic }
+
+    .MLink {
+        font-style: italic }
+
+    #MSelected {
+        color: #000000; background-color: #FFFFFF;
+        /*  Replace padding with border.  */
+        padding: 0 10px 0 10px;
+        border-width: 1px 2px 2px 0; border-style: solid; border-color: #000000;
+        margin-right: 5px }
+
+    /*  Close off the left side when its in a group.  */
+    .MGroup #MSelected {
+        padding-left: 9px; border-left-width: 1px }
+
+    /*  A treat for Mozilla users.  Blatantly non-standard.  Will be replaced with CSS 3 attributes when finalized/supported.  */
+    .Gecko #MSelected {
+        -moz-border-radius-topright: 10px;
+        -moz-border-radius-bottomright: 10px }
+    .Gecko .MGroup #MSelected {
+        -moz-border-radius-topleft: 10px;
+        -moz-border-radius-bottomleft: 10px }
+
+
+
+
+body.FramedContentPage,
+.ContentSection {
+    background-color: #FFFFFF;
+    padding-bottom: 15px }
+
+.ContentSection {
+    border-width: 0 0 1px 1px; border-style: solid; border-color: #000000 }
+
+
+    .CTopic {
+        font-size: 10pt;
+        /*  This should be a margin but Konq 3.1.1 sucks.  */
+        padding-bottom: 3em }
+
+
+    .CTitle {
+        font-size: 12pt; font-weight: bold;
+        border-width: 0 0 1px 0; border-style: solid; border-color: #A0A0A0;
+        margin: 0 15px .5em 15px }
+
+    .CGroup .CTitle {
+        font-size: 16pt; font-variant: small-caps;
+        padding-left: 15px; padding-right: 15px;
+        border-width: 0 0 2px 0; border-color: #000000;
+        margin-left: 0; margin-right: 0 }
+
+    .CClass .CTitle,
+    .CInterface .CTitle,
+    .CDatabase .CTitle,
+    .CDatabaseTable .CTitle,
+    .CSection .CTitle {
+        font-size: 18pt;
+        color: #FFFFFF; background-color: #A0A0A0;
+        padding: 10px 15px 10px 15px;
+        border-width: 2px 0; border-color: #000000;
+        margin-left: 0; margin-right: 0 }
+
+    #MainTopic .CTitle {
+        font-size: 20pt;
+        color: #FFFFFF; background-color: #7070C0;
+        padding: 10px 15px 10px 15px;
+        border-width: 0 0 3px 0; border-color: #000000;
+        margin-left: 0; margin-right: 0 }
+
+    .CBody {
+        margin-left: 15px; margin-right: 15px }
+
+
+    .CToolTip {
+        position: absolute; visibility: hidden;
+        left: 0; top: 0; max-width: 50%;
+        background-color: #FFFFE0;
+        padding: 5px;
+        border-width: 1px 2px 2px 1px; border-style: solid; border-color: #000000;
+        font-size: 8pt }
+
+    /*  Opera 6 gives it a huge height otherwise.  */
+    .Opera6 .CTooltip, .Opera5 .CTooltip {
+        max-width: 100% }
+
+    .CHeading {
+        font-weight: bold; font-size: 10pt;
+        margin-top: 1.5em; margin-bottom: .5em }
+
+    .CCode {
+        font: 10pt "Courier New", Courier, monospace;
+        overflow: auto;
+        }
+
+    .CBulletList {
+        /*  I don't know why CBody's margin doesn't apply, but it's consistent across browsers so whatever.
+             Reapply it here as padding.  */
+        padding-left: 15px; padding-right: 15px;
+        margin: .5em 5ex .5em 5ex;
+        }
+
+    .CDescriptionList {
+        margin: .5em 5ex 0 5ex }
+
+    /* IE 4 and Konqueror always makes it too long.  */
+    .IE4 .CDescriptionList,
+    .KHTML .CDescriptionList {
+        width: 85% }
+
+        .CDLEntry {
+            font: 10pt "Courier New", Courier, monospace; color: #808080;
+            padding-bottom: .25em;
+            white-space: nowrap }
+
+        .CDLDescription {
+            font-size: 10pt;  /*  For browsers that don't inherit correctly, like Opera 5.  */
+            padding-bottom: .5em; padding-left: 5ex }
+
+    .CTopic img {
+        text-align: center;
+        display: block;
+        margin: 1em auto;
+        }
+    .CImageCaption {
+        font-variant: small-caps;
+        font-size: 8pt;
+        color: #808080;
+        text-align: center;
+        position: relative;
+        top: 1em;
+        }
+
+    .CImageLink {
+        color: #808080;
+        font-style: italic;
+        }
+    a.CImageLink:link,
+    a.CImageLink:visited,
+    a.CImageLink:hover { color: #808080 }
+
+
+
+.Prototype {
+    font: 10pt "Courier New", Courier, monospace;
+    padding: 5px 3ex;
+    border-width: 1px; border-style: solid;
+    margin: 0 5ex 1.5em 5ex;
+    }
+
+    .Prototype td {
+        font-size: 10pt;
+        }
+
+    .PDefaultValue,
+    .PTypePrefix {
+        color: #8F8F8F;
+        }
+    .PTypePrefix {
+        text-align: right;
+        }
+
+    .IE .Prototype table {
+        padding: 0;
+        }
+
+    .CFunction .Prototype {
+        background-color: #F4F4F4; border-color: #D0D0D0 }
+    .CProperty .Prototype {
+        background-color: #F4F4FF; border-color: #C0C0E8 }
+    .CVariable .Prototype {
+        background-color: #FFFFF0; border-color: #E0E0A0 }
+
+    .CDatabaseIndex .Prototype,
+    .CConstant .Prototype {
+        background-color: #D0D0D0; border-color: #000000 }
+    .CType .Prototype {
+        background-color: #FFF8F8; border-color: #E8C8C8 }
+    .CDatabaseTrigger .Prototype,
+    .CEvent .Prototype,
+    .CDelegate .Prototype {
+        background-color: #F0FCF0; border-color: #B8E4B8 }
+
+    .CToolTip .Prototype {
+        margin: 0 0 .5em 0;
+        white-space: nowrap;
+        }
+
+
+
+
+
+.Summary {
+    margin: 1.5em 5ex 0 5ex }
+
+    .STitle {
+        font-size: 12pt; font-weight: bold;
+        margin-bottom: .5em }
+
+
+    .SBorder {
+        background-color: #FFFFF0;
+        padding: 15px;
+        border: 1px solid #C0C060 }
+
+    /* Let's observe the evolution of IE's brokeness, shall we?
+        IE 4 always makes them too long, there's no way around it.  */
+    .IE4 .SBorder {
+        width: 85% }
+    /* IE 5 will make them too long unless you set the width to 100%.  Isn't this implied for a div?  */
+    .IE5 .SBorder {
+        width: 100% }
+    /* IE 6 behaves like 5 when it's in a frame, but without frames it will be correct without a width or slightly too long
+        (but not enough to scroll) with a width.  This arbitrary weirdness simply astounds me.  */
+    body.FramedContentPage .IE6 .SBorder {
+        width: 100% }
+
+    /*  A treat for Mozilla users.  Blatantly non-standard.  Will be replaced with CSS 3 attributes when finalized/supported.  */
+    .Gecko .SBorder {
+        -moz-border-radius: 20px }
+
+
+    .STable {
+        font-size: 9pt; width: 100% }
+
+    .SEntrySize {
+        width: 30% }
+    .SDescriptionSize {
+        width: 70% }
+
+
+    .SMarked {
+        background-color: #F8F8D8 }
+
+
+    .SEntry .SIndent1 {
+        margin-left: 1.5ex }
+    .SEntry .SIndent2 {
+        margin-left: 3ex }
+    .SEntry .SIndent3 {
+        margin-left: 4.5ex }
+    .SEntry .SIndent4 {
+        margin-left: 6ex }
+    .SEntry .SIndent5 {
+        margin-left: 7.5ex }
+
+    .SDescription {
+        padding-left: 3ex }
+
+    .SDescription a { color: #800000}
+    .SDescription a:active { color: #A00000 }
+
+
+    .SGroup {
+        margin-top: .5em; margin-bottom: .25em }
+
+    .SGroup .SEntry {
+        font-weight: bold; font-variant: small-caps }
+
+    .SGroup .SEntry a { color: #800000 }
+    .SGroup .SEntry a:active { color: #F00000 }
+
+
+    .SMain .SEntry,
+    .SClass .SEntry,
+    .SDatabase .SEntry,
+    .SDatabaseTable .SEntry,
+    .SSection .SEntry {
+        font-weight: bold; font-size: 10pt;
+        margin-bottom: .25em }
+
+    .SClass,
+    .SDatabase,
+    .SDatabaseTable,
+    .SSection {
+        margin-top: 1em }
+
+    .SMain .SEntry a,
+    .SClass .SEntry a,
+    .SDatabase .SEntry a,
+    .SDatabaseTable .SEntry a,
+    .SSection .SEntry a { color: #000000 }
+
+    .SMain .SEntry a:active,
+    .SClass .SEntry a:active,
+    .SDatabase .SEntry a:active,
+    .SDatabaseTable .SEntry a:active,
+    .SSection .SEntry a:active { color: #A00000 }
+
+
+
+
+
+.ClassHierarchy {
+    margin: 0 15px 1em 15px }
+
+    .CHEntry {
+        border-width: 1px 2px 2px 1px; border-style: solid; border-color: #A0A0A0;
+        margin-bottom: 3px;
+        padding: 2px 2ex;
+        font-size: 10pt;
+        background-color: #F4F4F4; color: #606060;
+        }
+
+    .Gecko .CHEntry {
+        -moz-border-radius: 4px;
+        }
+
+    .CHCurrent .CHEntry {
+        font-weight: bold;
+        border-color: #000000;
+        color: #000000;
+        }
+
+    .CHChildNote .CHEntry {
+        font-style: italic;
+        font-size: 8pt;
+        }
+
+    .CHIndent {
+        margin-left: 3ex;
+        }
+
+    .CHEntry a:link,
+    .CHEntry a:visited,
+    .CHEntry a:hover {
+        color: #606060;
+        }
+    .CHEntry a:active {
+        color: #800000;
+        }
+
+
+
+
+
+body.FramedIndexPage,
+.IndexSection {
+    background-color: #FFFFFF;
+    font-size: 10pt;
+    padding: 15px }
+
+.IndexSection {
+    border-width: 0 0 1px 1px; border-style: solid; border-color: #000000 }
+
+    .IPageTitle {
+        font-size: 20pt; font-weight: bold;
+        color: #FFFFFF; background-color: #7070C0;
+        padding: 10px 15px 10px 15px;
+        border-width: 0 0 3px 0; border-color: #000000; border-style: solid;
+        margin: -15px -15px 0 -15px }
+
+    .INavigationBar {
+        text-align: center;
+        background-color: #FFFFF0;
+        padding: 5px;
+        border-bottom: solid 1px black;
+        margin: 0 -15px 15px -15px }
+
+    .INavigationBar a {
+        font-weight: bold }
+
+    .IHeading {
+        font-size: 16pt; font-weight: bold;
+        padding: 2.5em 0 .5em 0;
+        text-align: center;
+        width: 3.5ex;
+        }
+    #IFirstHeading {
+        padding-top: 0;
+        }
+
+    .IEntry {
+        padding-left: 1ex;  }
+
+    .ISubIndex {
+        padding-left: 3ex; padding-bottom: .5em }
+
+    /*  While it may cause some entries to look like links when they aren't, I found it's much easier to read the
+         index if everything's the same color.  */
+    .ISymbol {
+        font-weight: bold; color: #900000  }
+
+    .ISymbolPrefix {
+        text-align: right;
+        color: #C47C7C;
+        background-color: #F8F8F8;
+        border-right: 3px solid #E0E0E0;
+        border-left: 1px solid #E0E0E0;
+        padding: 0 1px 0 2px;
+        }
+    #IFirstSymbolPrefix {
+        border-top: 1px solid #E0E0E0;
+        }
+    #ILastSymbolPrefix {
+        border-bottom: 1px solid #E0E0E0;
+        }
+    #IOnlySymbolPrefix {
+        border-top: 1px solid #E0E0E0;
+        border-bottom: 1px solid #E0E0E0;
+        }
+
+    a.IParent,
+    a.IFile {
+        display: block;
+        }
+
+
+
+
+.Footer {
+    font-size: 8pt; color: #909090 }
+
+body.UnframedPage .Footer {
+    text-align: right;
+    margin: 2px }
+
+body.FramedMenuPage .Footer {
+    text-align: center;
+    margin: 5em 10px 0 10px}
+
+    .Footer a:link,
+    .Footer a:hover,
+    .Footer a:visited { color: #909090 }
+    .Footer a:active { color: #A00000 }

Added: trunk/build/NaturalDocs-1.4/Help/example/NaturalDocs.js
===================================================================
--- trunk/build/NaturalDocs-1.4/Help/example/NaturalDocs.js	                        (rev 0)
+++ trunk/build/NaturalDocs-1.4/Help/example/NaturalDocs.js	2009-07-01 15:30:00 UTC (rev 4275)
@@ -0,0 +1,204 @@
+
+//
+//  Browser Styles
+// ____________________________________________________________________________
+
+var agt=navigator.userAgent.toLowerCase();
+var browserType;
+var browserVer;
+
+if (agt.indexOf("opera") != -1)
+    {
+    browserType = "Opera";
+
+    if (agt.indexOf("opera 5") != -1 || agt.indexOf("opera/5") != -1)
+        {  browserVer = "Opera5";  }
+    else if (agt.indexOf("opera 6") != -1 || agt.indexOf("opera/6") != -1)
+        {  browserVer = "Opera6";  }
+    else if (agt.indexOf("opera 7") != -1 || agt.indexOf("opera/7") != -1)
+        {  browserVer = "Opera7";  }
+    }
+
+else if (agt.indexOf("khtml") != -1 || agt.indexOf("konq") != -1 || agt.indexOf("safari") != -1)
+    {
+    browserType = "KHTML";
+    }
+
+else if (agt.indexOf("msie") != -1)
+    {
+    browserType = "IE";
+
+    if (agt.indexOf("msie 4") != -1)
+        {  browserVer = "IE4";  }
+    else if (agt.indexOf("msie 5") != -1)
+        {  browserVer = "IE5";  }
+    else if (agt.indexOf("msie 6") != -1)
+        {  browserVer = "IE6";  }
+    }
+
+else if (agt.indexOf("gecko") != -1)
+    {
+    browserType = "Gecko";
+    }
+
+// Opera already taken care of.
+else if (agt.indexOf("mozilla") != -1 && agt.indexOf("compatible") == -1 && agt.indexOf("spoofer") == -1 &&
+           agt.indexOf("webtv") == -1 && agt.indexOf("hotjava") == -1)
+    {
+    browserType = "Netscape";
+
+    if (agt.indexOf("mozilla/4") != -1)
+        {  browserVer = "Netscape4";  }
+    }
+
+
+//
+//  Menu
+// ____________________________________________________________________________
+
+
+function ToggleMenu(id)
+    {
+    if (!window.document.getElementById)
+        {  return;  };
+
+    var display = window.document.getElementById(id).style.display;
+
+    if (display == "none")
+        {  display = "block";  }
+    else
+        {  display = "none";  }
+
+    window.document.getElementById(id).style.display = display;
+    }
+
+
+//
+//  Tooltips
+// ____________________________________________________________________________
+
+
+var tooltipTimer = 0;
+
+function ShowTip(event, tooltipID, linkID)
+    {
+    if (tooltipTimer)
+        {  clearTimeout(tooltipTimer);  };
+
+    var docX = event.clientX + window.pageXOffset;
+    var docY = event.clientY + window.pageYOffset;
+
+    var showCommand = "ReallyShowTip('" + tooltipID + "', '" + linkID + "', " + docX + ", " + docY + ")";
+
+    // KHTML cant handle showing on a timer right now.
+
+    if (browserType != "KHTML")
+        {  tooltipTimer = setTimeout(showCommand, 1000);  }
+    else
+        {  eval(showCommand);  };
+    }
+
+function ReallyShowTip(tooltipID, linkID, docX, docY)
+    {
+    tooltipTimer = 0;
+
+    var tooltip;
+    var link;
+
+    if (document.getElementById)
+        {
+        tooltip = document.getElementById(tooltipID);
+        link = document.getElementById(linkID);
+        }
+    else if (document.all)
+        {
+        tooltip = eval("document.all['" + tooltipID + "']");
+        link = eval("document.all['" + linkID + "']");
+        }
+
+    if (tooltip)
+        {
+        var left = 0;
+        var top = 0;
+
+        // Not everything supports offsetTop/Left/Width, and some, like Konqueror and Opera 5, think they do but do it badly.
+
+        if (link && link.offsetWidth != null && browserType != "KHTML" && browserVer != "Opera5")
+            {
+            var item = link;
+            while (item != document.body)
+                {
+                left += item.offsetLeft;
+                item = item.offsetParent;
+                }
+
+            item = link;
+            while (item != document.body)
+                {
+                top += item.offsetTop;
+                item = item.offsetParent;
+                }
+            top += link.offsetHeight;
+            }
+
+        // The fallback method is to use the mouse X and Y relative to the document.  We use a separate if and test if its a number
+        // in case some browser snuck through the above if statement but didn't support everything.
+
+        if (!isFinite(top) || top == 0)
+            {
+            left = docX;
+            top = docY;
+            }
+
+        // Some spacing to get it out from under the cursor.
+
+        top += 10;
+
+        // Make sure the tooltip doesnt get smushed by being too close to the edge, or in some browsers, go off the edge of the
+        // page.  We do it here because Konqueror does get offsetWidth right even if it doesnt get the positioning right.
+
+        if (tooltip.offsetWidth != null)
+            {
+            var width = tooltip.offsetWidth;
+            var docWidth = document.body.clientWidth;
+
+            if (left + width > docWidth)
+                {  left = docWidth - width - 1;  }
+            }
+
+        // Opera 5 chokes on the px extension, so it can use the Microsoft one instead.
+
+        if (tooltip.style.left != null && browserVer != "Opera5")
+            {
+            tooltip.style.left = left + "px";
+            tooltip.style.top = top + "px";
+            }
+        else if (tooltip.style.pixelLeft != null)
+            {
+            tooltip.style.pixelLeft = left;
+            tooltip.style.pixelTop = top;
+            }
+
+        tooltip.style.visibility = "visible";
+        }
+    }
+
+function HideTip(tooltipID)
+    {
+    if (tooltipTimer)
+        {
+        clearTimeout(tooltipTimer);
+        tooltipTimer = 0;
+        }
+
+    var tooltip;
+
+    if (document.getElementById)
+        {  tooltip = document.getElementById(tooltipID); }
+    else if (document.all)
+        {  tooltip = eval("document.all['" + tooltipID + "']");  }
+
+    if (tooltip)
+        {  tooltip.style.visibility = "hidden";  }
+    }
+

Added: trunk/build/NaturalDocs-1.4/Help/examples.css
===================================================================
--- trunk/build/NaturalDocs-1.4/Help/examples.css	                        (rev 0)
+++ trunk/build/NaturalDocs-1.4/Help/examples.css	2009-07-01 15:30:00 UTC (rev 4275)
@@ -0,0 +1,90 @@
+ at import URL(example/Default.css);
+
+
+.NDContent {
+    color: #000000; background-color: #FFFFFF;
+    padding: 15px 0;
+    border-style: solid;
+    border-width: 1px 3px 3px 1px;
+    border-color: #c0c0c0 #808080 #808080 #c0c0c0;
+    margin: 1em 5ex;
+    -moz-border-radius: 12px;
+    }
+
+    .NDContent p,
+    .NDContent li,
+    .NDContent td,
+    .NDMenu td,
+    .NDSummary td,
+    .NDIndex td {
+        font-size: 10pt;
+        line-height: normal;
+        }
+    .NDContent .CTopic {
+        padding-bottom: 0;
+        }
+    .Prototype td {
+        font: 10pt Courier New, monospace;
+        }
+    .NDIndex .IHeading {
+        font-size: 16pt;
+        }
+
+
+.NDMenu {
+    font: 9pt Verdana, Arial, sans-serif;
+    color: #000000; background-color: #E8E8E8;
+    width: 27ex;
+    padding: 10px 0;
+    border-style: solid;
+    border-width: 1px 3px 3px 1px;
+    border-color: #808080 #606060 #606060 #808080;
+    margin: 1em 0 1em 5ex;
+    -moz-border-radius: 12px;
+    }
+
+
+.NDFooter {
+    font: 8pt Verdana, Arial, sans-serif;
+    color: #909090; background-color: #E8E8E8;
+    border-style: solid;
+    border-width: 1px 3px 3px 1px;
+    border-color: #808080 #606060 #606060 #808080;
+    margin: 1em 0 1em 5ex;
+    -moz-border-radius: 12px;
+    }
+.NDFooter td {
+	font-size: 8pt;
+    padding: 0 2ex;
+	}
+
+    .NDFooter a:link,
+    .NDFooter a:hover,
+    .NDFooter a:visited { color: #909090 }
+    .NDFooter a:active { color: #A00000 }
+
+
+.NDSummary {
+    padding: 15px;
+    border-style: solid;
+    border-width: 1px 3px 3px 1px;
+    border-color: #c0c0c0 #808080 #808080 #c0c0c0;
+    margin: 1em 5ex;
+    -moz-border-radius: 12px;
+    }
+
+    .NDSummary .Summary {
+        margin-top: 0;
+        }
+
+
+.NDIndex {
+    color: #000000; background-color: #FFFFFF;
+    padding: 15px;
+    border-style: solid;
+    border-width: 1px 3px 3px 1px;
+    border-color: #c0c0c0 #808080 #808080 #c0c0c0;
+    margin: 1em 5ex;
+    -moz-border-radius: 12px;
+    }
+

Added: trunk/build/NaturalDocs-1.4/Help/images/header/background.png
===================================================================
(Binary files differ)


Property changes on: trunk/build/NaturalDocs-1.4/Help/images/header/background.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/build/NaturalDocs-1.4/Help/images/header/leftside.png
===================================================================
(Binary files differ)


Property changes on: trunk/build/NaturalDocs-1.4/Help/images/header/leftside.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/build/NaturalDocs-1.4/Help/images/header/logo.png
===================================================================
(Binary files differ)


Property changes on: trunk/build/NaturalDocs-1.4/Help/images/header/logo.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/build/NaturalDocs-1.4/Help/images/header/overbody.png
===================================================================
(Binary files differ)


Property changes on: trunk/build/NaturalDocs-1.4/Help/images/header/overbody.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/build/NaturalDocs-1.4/Help/images/header/overbodybg.png
===================================================================
(Binary files differ)


Property changes on: trunk/build/NaturalDocs-1.4/Help/images/header/overbodybg.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/build/NaturalDocs-1.4/Help/images/header/overleftmargin.png
===================================================================
(Binary files differ)


Property changes on: trunk/build/NaturalDocs-1.4/Help/images/header/overleftmargin.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/build/NaturalDocs-1.4/Help/images/header/overmenu.png
===================================================================
(Binary files differ)


Property changes on: trunk/build/NaturalDocs-1.4/Help/images/header/overmenu.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/build/NaturalDocs-1.4/Help/images/header/overmenubg.png
===================================================================
(Binary files differ)


Property changes on: trunk/build/NaturalDocs-1.4/Help/images/header/overmenubg.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/build/NaturalDocs-1.4/Help/images/header/rightside.png
===================================================================
(Binary files differ)


Property changes on: trunk/build/NaturalDocs-1.4/Help/images/header/rightside.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/build/NaturalDocs-1.4/Help/images/logo.gif
===================================================================
(Binary files differ)


Property changes on: trunk/build/NaturalDocs-1.4/Help/images/logo.gif
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/build/NaturalDocs-1.4/Help/images/menu/about.png
===================================================================
(Binary files differ)


Property changes on: trunk/build/NaturalDocs-1.4/Help/images/menu/about.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/build/NaturalDocs-1.4/Help/images/menu/background.png
===================================================================
(Binary files differ)


Property changes on: trunk/build/NaturalDocs-1.4/Help/images/menu/background.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/build/NaturalDocs-1.4/Help/images/menu/bottomleft.png
===================================================================
(Binary files differ)


Property changes on: trunk/build/NaturalDocs-1.4/Help/images/menu/bottomleft.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/build/NaturalDocs-1.4/Help/images/menu/bottomright.png
===================================================================
(Binary files differ)


Property changes on: trunk/build/NaturalDocs-1.4/Help/images/menu/bottomright.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/build/NaturalDocs-1.4/Help/images/menu/community.png
===================================================================
(Binary files differ)


Property changes on: trunk/build/NaturalDocs-1.4/Help/images/menu/community.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/build/NaturalDocs-1.4/Help/images/menu/customizing.png
===================================================================
(Binary files differ)


Property changes on: trunk/build/NaturalDocs-1.4/Help/images/menu/customizing.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/build/NaturalDocs-1.4/Help/images/menu/using.png
===================================================================
(Binary files differ)


Property changes on: trunk/build/NaturalDocs-1.4/Help/images/menu/using.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/build/NaturalDocs-1.4/Help/index.html
===================================================================
--- trunk/build/NaturalDocs-1.4/Help/index.html	                        (rev 0)
+++ trunk/build/NaturalDocs-1.4/Help/index.html	2009-07-01 15:30:00 UTC (rev 4275)
@@ -0,0 +1,9 @@
+
+
+<html><head><title>Natural Docs</title><link rel=stylesheet type="text/css" href="styles.css"><script language=JavaScript src="javascript/PNGHandling.js"></script><script language=JavaScript src="javascript/BrowserStyles.js"></script></head><body marginwidth=0 marginheight=0 leftmargin=0 topmargin=0><script language=JavaScript><!--
+OpeningBrowserTags();// --></script>
+
+<!-- saved from url=(0026)http://www.naturaldocs.org -->
+
+<table width=100% border=0 cellspacing=0 cellpadding=0><tr><td colspan=3 class=Header><table width=100% border=0 cellspacing=0 cellpadding=0><tr><td><img src="images/header/leftside.png" width=30 height=75><a href="index.html"><img src="images/header/logo.png" width=524 height=75 alt="Natural Docs"></a></td><td align=right><img src="images/header/rightside.png" width=30 height=75></td></tr></table></td></tr><tr><td><img src="images/header/overleftmargin.png" width=10 height=6></td><td class=SideMenuTop><img src="images/header/overmenu.png" width=14 height=6></td><td class=BodyTop><img src="images/header/overbody.png" width=24 height=6></td></tr><tr><td></td><td class=SideMenu nowrap><div class=SideMenuSection><div class=SideMenuTitle><img src="images/menu/about.png" width=52 height=13 alt="About"></div><div class=SideMenuBody><a href="languages.html" class=SideMenuEntry>Language Support</a><a href="output.html" class=SideMenuEntry>Output Formats</a></div></div><div class=SideMenuSection><div class=SideMenuTitle><img src="images/menu/using.png" width=45 height=13 alt="Using"></div><div class=SideMenuBody><a href="documenting.html" class=SideMenuEntry>Documenting<br>Your Code</a><a href="keywords.html" class=SideMenuEntry>Keywords</a><a href="running.html" class=SideMenuEntry>Running</a><a href="troubleshooting.html" class=SideMenuEntry>Troubleshooting</a></div></div><div class=SideMenuSection><div class=SideMenuTitle><img src="images/menu/customizing.png" width=96 height=13 alt="Customizing"></div><div class=SideMenuBody><a href="menu.html" class=SideMenuEntry>Organizing the Menu</a><a href="styles.html" class=SideMenuEntry>CSS Styles</a><a href="customizingtopics.html" class=SideMenuEntry>Topics and Keywords</a><a href="customizinglanguages.html" class=SideMenuEntry>Languages, Indexes,<br>and Prototypes</a></div></div><div class=SideMenuSection><div class=SideMenuTitle><img src="images/menu/community.png" width=86 height=13 alt="Community"></div><div class=SideMenuBody><a href="http://www.naturaldocs.org/" class=SideMenuEntry>Web Site</a><a href="http://www.naturaldocs.org/mailinglist.html" class=SideMenuEntry>Mailing Lists</a><a href="http://www.naturaldocs.org/messageboards.html" class=SideMenuEntry>Message Boards</a><a href="http://www.naturaldocs.org/bugs.html" class=SideMenuEntry>Bugs and<br>Feature Requests</a></div></div></td><td class=Body width=100%><div class=PageTitle>Version 1.4</div><div class=Topic><p>This is the Natural Docs help file, a subset of the documentation available at <a href="http://www.naturaldocs.org">the web site</a>.&nbsp; Everything you need is on the menu to the left.</p></div></td></tr><tr><td></td><td class=SideMenuBottom><table width=100% border=0 cellspacing=0 cellpadding=0><tr><td class=SideMenuBottomLeft><img src="images/menu/bottomleft.png" width=18 height=19></td><td class=SideMenuBottomRight><img src="images/menu/bottomright.png" width=18 height=19></td></tr></table></td><td class=BodyBottom>Copyright &copy; 2003-2008 Greg Valure</td></tr></table><script language=JavaScript><!--
+ClosingBrowserTags();// --></script></body></html>
\ No newline at end of file

Added: trunk/build/NaturalDocs-1.4/Help/javascript/BrowserStyles.js
===================================================================
--- trunk/build/NaturalDocs-1.4/Help/javascript/BrowserStyles.js	                        (rev 0)
+++ trunk/build/NaturalDocs-1.4/Help/javascript/BrowserStyles.js	2009-07-01 15:30:00 UTC (rev 4275)
@@ -0,0 +1,77 @@
+
+//
+//  Browser Styles
+// ____________________________________________________________________________
+
+var agt=navigator.userAgent.toLowerCase();
+var browserType;
+var browserVer;
+
+if (agt.indexOf("opera") != -1)
+    {
+    browserType = "Opera";
+
+    if (agt.indexOf("opera 5") != -1 || agt.indexOf("opera/5") != -1)
+        {  browserVer = "Opera5";  }
+    else if (agt.indexOf("opera 6") != -1 || agt.indexOf("opera/6") != -1)
+        {  browserVer = "Opera6";  }
+    else if (agt.indexOf("opera 7") != -1 || agt.indexOf("opera/7") != -1)
+        {  browserVer = "Opera7";  }
+    }
+
+else if (agt.indexOf("khtml") != -1 || agt.indexOf("konq") != -1 || agt.indexOf("safari") != -1)
+    {
+    browserType = "KHTML";
+    }
+
+else if (agt.indexOf("msie") != -1)
+    {
+    browserType = "IE";
+
+    if (agt.indexOf("msie 4") != -1)
+        {  browserVer = "IE4";  }
+    else if (agt.indexOf("msie 5") != -1)
+        {  browserVer = "IE5";  }
+    else if (agt.indexOf("msie 6") != -1)
+        {  browserVer = "IE6";  }
+    else if (agt.indexOf("msie 7") != -1)
+        {  browserVer = "IE7";  }
+    }
+
+else if (agt.indexOf("gecko") != -1)
+    {
+    browserType = "Gecko";
+    }
+
+// Opera already taken care of.
+else if (agt.indexOf("mozilla") != -1 && agt.indexOf("compatible") == -1 && agt.indexOf("spoofer") == -1 &&
+           agt.indexOf("webtv") == -1 && agt.indexOf("hotjava") == -1)
+    {
+    browserType = "Netscape";
+
+    if (agt.indexOf("mozilla/4") != -1)
+        {  browserVer = "Netscape4";  }
+    }
+
+
+function OpeningBrowserTags()
+    {
+    if (browserType)
+        {
+        document.write('<div class='+browserType+'>');
+
+        if (browserVer)
+            {  document.write('<div class='+browserVer+'>');  }
+        }
+    };
+
+function ClosingBrowserTags()
+    {
+    if (browserType)
+        {
+        document.write('</div>');
+
+        if (browserVer)
+            {  document.write('</div>');  }
+        }
+    };

Added: trunk/build/NaturalDocs-1.4/Help/javascript/PNGHandling.js
===================================================================
--- trunk/build/NaturalDocs-1.4/Help/javascript/PNGHandling.js	                        (rev 0)
+++ trunk/build/NaturalDocs-1.4/Help/javascript/PNGHandling.js	2009-07-01 15:30:00 UTC (rev 4275)
@@ -0,0 +1,72 @@
+// Parts derived from:
+//    Opacity Displayer, Version 1.0
+//    Copyright Michael Lovitt, 6/2002.
+//    Distribute freely, but please leave this notice intact.
+//    http://www.alistapart.com/articles/pngopacity/
+
+// Parts derived from:
+//    Natural Docs
+//    Copyright (C) 2003-2004 Greg Valure
+//    http://www.naturaldocs.org/
+
+
+var pngTransform;
+var pngNormal;
+
+var agt=navigator.userAgent.toLowerCase();
+
+if (agt.indexOf("opera") != -1)
+    {
+    if ( (agt.indexOf("opera 5") != -1 || agt.indexOf("opera/5") != -1) &&
+         agt.indexOf("mac") != -1)
+        {
+        pngNormal = 1;
+        }
+    else if (agt.indexOf("opera 6") != -1 || agt.indexOf("opera/6") != -1 ||
+               agt.indexOf("opera 7") != -1 || agt.indexOf("opera/7") != -1)
+        {
+        pngNormal = 1;
+        }
+    }
+
+else if (agt.indexOf("msie") != -1)
+    {
+    if (agt.indexOf("msie 5.5") != -1 || agt.indexOf("msie 6") != -1)
+        {
+        if (agt.indexOf("mac") != -1)
+            {  pngNormal = 1;  }
+        else if (agt.indexOf("win") != -1)
+            {  pngTransform = 1;  };
+        }
+
+    else if (agt.indexOf("msie 5") != -1)
+        {
+        if (agt.indexOf("mac") != -1)
+            {  pngNormal = 1;  };
+        }
+
+    else if (agt.indexOf("msie 7") != -1)
+        {  pngNormal = 1;  }
+    }
+
+else if (agt.indexOf("gecko") != -1)
+    {
+    pngNormal = 1;
+    }
+
+
+function PNGGIF(strPath, intWidth, intHeight, strAlt, strID)
+    {
+    if (pngTransform)
+        {
+        document.write('<div style="height:'+intHeight+'px;width:'+intWidth+'px;filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src=\''+strPath+'.png\', sizingMethod=\'scale\')" id="'+strID+'"></div>');
+        }
+    else if (pngNormal)
+        {
+        document.write('<img src="'+strPath+'.png" width="'+intWidth+'" height="'+intHeight+'" alt="'+strAlt+'" id="'+strID+'"/>');
+        }
+    else
+        {
+        document.write('<img src="'+strPath+'.gif" width="'+intWidth+'" height="'+intHeight+'" alt="'+strAlt+'" id="'+strID+'" />');
+        }
+    };

Added: trunk/build/NaturalDocs-1.4/Help/keywords.html
===================================================================
--- trunk/build/NaturalDocs-1.4/Help/keywords.html	                        (rev 0)
+++ trunk/build/NaturalDocs-1.4/Help/keywords.html	2009-07-01 15:30:00 UTC (rev 4275)
@@ -0,0 +1,38 @@
+
+
+<html><head><title>Natural Docs Topics and Keywords</title><link rel=stylesheet type="text/css" href="styles.css"><style type="text/css"><!--
+
+
+        .TopicType {
+            margin-bottom: 3em;
+            }
+
+        .TopicTypeName {
+            font: bold 12pt Georgia, serif;
+            margin-bottom: .5em;
+            }
+        .Behavior {
+            font: italic 8pt Georgia, serif;
+            margin-top: -.75em;
+            margin-bottom: 1em;
+            color: #808080;
+            }
+
+
+        .TopicTypes td {
+            width: 33%;
+            }
+
+        .Keywords td {
+            padding-right: 2ex;
+            width: auto;
+            }
+
+    
+--></style><script language=JavaScript src="javascript/PNGHandling.js"></script><script language=JavaScript src="javascript/BrowserStyles.js"></script></head><body marginwidth=0 marginheight=0 leftmargin=0 topmargin=0><script language=JavaScript><!--
+OpeningBrowserTags();// --></script>
+
+<!-- saved from url=(0026)http://www.naturaldocs.org -->
+
+<table width=100% border=0 cellspacing=0 cellpadding=0><tr><td colspan=3 class=Header><table width=100% border=0 cellspacing=0 cellpadding=0><tr><td><img src="images/header/leftside.png" width=30 height=75><a href="index.html"><img src="images/header/logo.png" width=524 height=75 alt="Natural Docs"></a></td><td align=right><img src="images/header/rightside.png" width=30 height=75></td></tr></table></td></tr><tr><td><img src="images/header/overleftmargin.png" width=10 height=6></td><td class=SideMenuTop><img src="images/header/overmenu.png" width=14 height=6></td><td class=BodyTop><img src="images/header/overbody.png" width=24 height=6></td></tr><tr><td></td><td class=SideMenu nowrap><div class=SideMenuSection><div class=SideMenuTitle><img src="images/menu/about.png" width=52 height=13 alt="About"></div><div class=SideMenuBody><a href="languages.html" class=SideMenuEntry>Language Support</a><a href="output.html" class=SideMenuEntry>Output Formats</a></div></div><div class=SideMenuSection><div class=SideMenuTitle><img src="images/menu/using.png" width=45 height=13 alt="Using"></div><div class=SideMenuBody><a href="documenting.html" class=SideMenuEntry>Documenting<br>Your Code</a><span class=SideMenuEntry id=SelectedSideMenuEntry>Keywords</span><a href="running.html" class=SideMenuEntry>Running</a><a href="troubleshooting.html" class=SideMenuEntry>Troubleshooting</a></div></div><div class=SideMenuSection><div class=SideMenuTitle><img src="images/menu/customizing.png" width=96 height=13 alt="Customizing"></div><div class=SideMenuBody><a href="menu.html" class=SideMenuEntry>Organizing the Menu</a><a href="styles.html" class=SideMenuEntry>CSS Styles</a><a href="customizingtopics.html" class=SideMenuEntry>Topics and Keywords</a><a href="customizinglanguages.html" class=SideMenuEntry>Languages, Indexes,<br>and Prototypes</a></div></div><div class=SideMenuSection><div class=SideMenuTitle><img src="images/menu/community.png" width=86 height=13 alt="Community"></div><div class=SideMenuBody><a href="http://www.naturaldocs.org/" class=SideMenuEntry>Web Site</a><a href="http://www.naturaldocs.org/mailinglist.html" class=SideMenuEntry>Mailing Lists</a><a href="http://www.naturaldocs.org/messageboards.html" class=SideMenuEntry>Message Boards</a><a href="http://www.naturaldocs.org/bugs.html" class=SideMenuEntry>Bugs and<br>Feature Requests</a></div></div></td><td class=Body width=100%><div class=PageTitle>Topics and Keywords</div><div class=TOC><a href="#General">General Topics</a> &middot; <a href="#Code">Code Topics</a> &middot; <a href="#Database">Database Topics</a> &middot; <a href="#Misc">Miscellaneous Topics</a></div><div class=Topic><p>Keywords are not case sensitive and are interchangable within their topic type.&nbsp; The plural forms denote <a href="documenting/reference.html#ListTopics">list topics</a> where every item in its <a href="documenting/reference.html#DefinitionLists">definition lists</a> are treated like they have their own topic.</p></div><div class=Topic><a name=General></a><div class=TopicTitle>General Topics</div><table width=100% border=0 cellspacing=0 cellpadding=0 class=TopicTypes><tr><td><div class=TopicType><div class=TopicTypeName>Generic</div><table border=0 cellspacing=0 cellpadding=0 class=Keywords><tr><td>topic</td><td>topics</td></tr><tr><td>about</td><td>list</td></tr></table></div></td><td><div class=TopicType><div class=TopicTypeName>Section</div><div class=Behavior>Ends Scope</div><table border=0 cellspacing=0 cellpadding=0 class=Keywords><tr><td>section</td><td></td></tr><tr><td>title</td><td></td></tr></table></div><div class=TopicType><div class=TopicTypeName>Group</div><table border=0 cellspacing=0 cellpadding=0 class=Keywords><tr><td>group</td><td></td></tr></table></div></td><td><div class=TopicType><div class=TopicTypeName>File</div><div class=Behavior>Always Global</div><table border=0 cellspacing=0 cellpadding=0 class=Keywords><tr><td>file</td><td>files</td></tr><tr><td>program</td><td>programs</td></tr><tr><td>script</td><td>scripts</td></tr><tr><td>document</td><td>documents</td></tr><tr><td>doc</td><td>docs</td></tr><tr><td>header</td><td>headers</td></tr></table></div></td></tr></table></div><div class=Topic><a name=Code></a><div class=TopicTitle>Code Topics</div><table width=100% border=0 cellspacing=0 cellpadding=0 class=TopicTypes><tr><td><div class=TopicType><div class=TopicTypeName>Class</div><div class=Behavior>Starts Scope</div><table border=0 cellspacing=0 cellpadding=0 class=Keywords><tr><td>class</td><td>classes</td></tr><tr><td>structure</td><td>structures</td></tr><tr><td>struct</td><td>structs</td></tr><tr><td>package</td><td>packages</td></tr><tr><td>namespace</td><td>namespaces</td></tr></table></div><div class=TopicType><div class=TopicTypeName>Interface</div><div class=Behavior>Starts Scope</div><table border=0 cellspacing=0 cellpadding=0 class=Keywords><tr><td>interface</td><td>interfaces</td></tr></table></div><div class=TopicType><div class=TopicTypeName>Type</div><table border=0 cellspacing=0 cellpadding=0 class=Keywords><tr><td>type</td><td>types</td></tr><tr><td>typedef</td><td>typedefs</td></tr></table></div><div class=TopicType><div class=TopicTypeName>Constant</div><table border=0 cellspacing=0 cellpadding=0 class=Keywords><tr><td>constant</td><td>constants</td></tr><tr><td>const</td><td>consts</td></tr></table></div><div class=TopicType><div class=TopicTypeName>Enumeration</div><div class=Behavior>Topic indexed under Types<br>Members indexed under Constants</div><table border=0 cellspacing=0 cellpadding=0 class=Keywords><tr><td>enumeration</td><td>enumerations</td></tr><tr><td>enum</td><td>enums</td></tr></table></div></td><td><div class=TopicType><div class=TopicTypeName>Function</div><div class=Behavior>List topics break apart</div><table border=0 cellspacing=0 cellpadding=0 class=Keywords><tr><td>function</td><td>functions</td></tr><tr><td>func</td><td>funcs</td></tr><tr><td>procedure</td><td>procedures</td></tr><tr><td>proc</td><td>procs</td></tr><tr><td>routine</td><td>routines</td></tr><tr><td>subroutine</td><td>subroutines</td></tr><tr><td>sub</td><td>subs</td></tr><tr><td>method</td><td>methods</td></tr><tr><td>callback</td><td>callbacks</td></tr><tr><td>constructor</td><td>constructors</td></tr><tr><td>destructor</td><td>destructors</td></tr><tr><td>operator</td><td>operators</td></tr></table></div><div class=TopicType><div class=TopicTypeName>Property</div><table border=0 cellspacing=0 cellpadding=0 class=Keywords><tr><td>property</td><td>properties</td></tr><tr><td>prop</td><td>props</td></tr></table></div><div class=TopicType><div class=TopicTypeName>Event</div><table border=0 cellspacing=0 cellpadding=0 class=Keywords><tr><td>event</td><td>events</td></tr></table></div><div class=TopicType><div class=TopicTypeName>Delegate</div><table border=0 cellspacing=0 cellpadding=0 class=Keywords><tr><td>delegate</td><td>delegates</td></tr></table></div><div class=TopicType><div class=TopicTypeName>Macro</div><table border=0 cellspacing=0 cellpadding=0 class=Keywords><tr><td>macro</td><td>macros</td></tr><tr><td>define</td><td>defines</td></tr><tr><td>def</td><td>defs</td></tr></table></div></td><td><div class=TopicType><div class=TopicTypeName>Variable</div><table border=0 cellspacing=0 cellpadding=0 class=Keywords><tr><td>variable</td><td>variables</td></tr><tr><td>var</td><td>vars</td></tr><tr><td>integer</td><td>integers</td></tr><tr><td>int</td><td>ints</td></tr><tr><td>uint</td><td>uints</td></tr><tr><td>long</td><td>longs</td></tr><tr><td>ulong</td><td>ulongs</td></tr><tr><td>short</td><td>shorts</td></tr><tr><td>ushort</td><td>ushorts</td></tr><tr><td>byte</td><td>bytes</td></tr><tr><td>ubyte</td><td>ubytes</td></tr><tr><td>sbyte</td><td>sbytes</td></tr><tr><td>float</td><td>floats</td></tr><tr><td>double</td><td>doubles</td></tr><tr><td>real</td><td>reals</td></tr><tr><td>decimal</td><td>decimals</td></tr><tr><td>scalar</td><td>scalars</td></tr><tr><td>array</td><td>arrays</td></tr><tr><td>arrayref</td><td>arrayrefs</td></tr><tr><td>hash</td><td>hashes</td></tr><tr><td>hashref</td><td>hashrefs</td></tr><tr><td>bool</td><td>bools</td></tr><tr><td>boolean</td><td>booleans</td></tr><tr><td>flag</td><td>flags</td></tr><tr><td>bit</td><td>bits</td></tr><tr><td>bitfield</td><td>bitfields</td></tr><tr><td>field</td><td>fields</td></tr><tr><td>pointer</td><td>pointers</td></tr><tr><td>ptr</td><td>ptrs</td></tr><tr><td>reference</td><td>references</td></tr><tr><td>ref</td><td>refs</td></tr><tr><td>object</td><td>objects</td></tr><tr><td>obj</td><td>objs</td></tr><tr><td>character</td><td>characters</td></tr><tr><td>wcharacter</td><td>wcharacters</td></tr><tr><td>char</td><td>chars</td></tr><tr><td>wchar</td><td>wchars</td></tr><tr><td>string</td><td>strings</td></tr><tr><td>wstring</td><td>wstrings</td></tr><tr><td>str</td><td>strs</td></tr><tr><td>wstr</td><td>wstrs</td></tr><tr><td>handle</td><td>handles</td></tr></table></div></td></tr></table></div><div class=Topic><a name=Database></a><div class=TopicTitle>Database Topics</div><table width=100% border=0 cellspacing=0 cellpadding=0 class=TopicTypes><tr><td><div class=TopicType><div class=TopicTypeName>Database</div><table border=0 cellspacing=0 cellpadding=0 class=Keywords><tr><td>database</td><td>databases</td></tr><tr><td>db</td><td>dbs</td></tr></table></div><div class=TopicType><div class=TopicTypeName>Database Table</div><div class=Behavior>Starts Scope</div><table border=0 cellspacing=0 cellpadding=0 class=Keywords><tr><td>table</td><td>tables</td></tr><tr><td>database table</td><td>database tables</td></tr><tr><td>db table</td><td>db tables</td></tr></table></div><div class=TopicType><div class=TopicTypeName>Database View</div><div class=Behavior>Starts Scope</div><table border=0 cellspacing=0 cellpadding=0 class=Keywords><tr><td>view</td><td>views</td></tr><tr><td>database view</td><td>database views</td></tr><tr><td>db view</td><td>db views</td></tr></table></div><div class=TopicType><div class=TopicTypeName>Database Cursor</div><table border=0 cellspacing=0 cellpadding=0 class=Keywords><tr><td>cursor</td><td>cursors</td></tr><tr><td>database cursor</td><td>database cursors</td></tr><tr><td>db cursor</td><td>db cursors</td></tr></table></div></td><td><div class=TopicType><div class=TopicTypeName>Database Index</div><table border=0 cellspacing=0 cellpadding=0 class=Keywords><tr><td>index</td><td>indexes</td></tr><tr><td></td><td>indices</td></tr><tr><td>database index</td><td>database indexes</td></tr><tr><td></td><td>database indices</td></tr><tr><td>db index</td><td>db indexes</td></tr><tr><td></td><td>db indices</td></tr><tr><td>key</td><td>keys</td></tr><tr><td>database key</td><td>database keys</td></tr><tr><td>db key</td><td>db keys</td></tr><tr><td>primary key</td><td>primary keys</td></tr><tr><td>database primary key</td><td>database primary keys</td></tr><tr><td>db primary key</td><td>db primary keys</td></tr></table></div><div class=TopicType><div class=TopicTypeName>Database Trigger</div><table border=0 cellspacing=0 cellpadding=0 class=Keywords><tr><td>trigger</td><td>triggers</td></tr><tr><td>database trigger</td><td>database triggers</td></tr><tr><td>db trigger</td><td>db triggers</td></tr></table></div></td></tr></table></div><div class=Topic><a name=Misc></a><div class=TopicTitle>Miscellaneous Topics</div><table width=100% border=0 cellspacing=0 cellpadding=0 class=TopicTypes><tr><td><div class=TopicType><div class=TopicTypeName>Cookie</div><div class=Behavior>Always global</div><table border=0 cellspacing=0 cellpadding=0 class=Keywords><tr><td>cookie</td><td>cookies</td></tr></table></div></td><td><div class=TopicType><div class=TopicTypeName>Build Target</div><table border=0 cellspacing=0 cellpadding=0 class=Keywords><tr><td>target</td><td>targets</td></tr><tr><td>build target</td><td>build targets</td></tr></table></div></td></tr></table></div></td></tr><tr><td></td><td class=SideMenuBottom><table width=100% border=0 cellspacing=0 cellpadding=0><tr><td class=SideMenuBottomLeft><img src="images/menu/bottomleft.png" width=18 height=19></td><td class=SideMenuBottomRight><img src="images/menu/bottomright.png" width=18 height=19></td></tr></table></td><td class=BodyBottom>Copyright &copy; 2003-2008 Greg Valure</td></tr></table><script language=JavaScript><!--
+ClosingBrowserTags();// --></script></body></html>
\ No newline at end of file

Added: trunk/build/NaturalDocs-1.4/Help/languages.html
===================================================================
--- trunk/build/NaturalDocs-1.4/Help/languages.html	                        (rev 0)
+++ trunk/build/NaturalDocs-1.4/Help/languages.html	2009-07-01 15:30:00 UTC (rev 4275)
@@ -0,0 +1,32 @@
+
+
+<html><head><title>Natural Docs Language Support</title><link rel=stylesheet type="text/css" href="styles.css"><style type="text/css"><!--
+
+
+        ul.LanguageList li {
+           font: 12pt Georgia, serif;
+           margin-bottom: .25em;
+           }
+
+        ul.LanguageList .Subtle {
+           font-size: 10pt;
+           }
+
+        .NextUp {
+           color: #808080;
+           font: 9pt Verdana, sans-serif;
+           margin-left: 1ex }
+
+        .LastUpdated {
+            margin-left: 3.5ex;
+            }
+
+
+    
+--></style><script language=JavaScript src="javascript/PNGHandling.js"></script><script language=JavaScript src="javascript/BrowserStyles.js"></script></head><body marginwidth=0 marginheight=0 leftmargin=0 topmargin=0><script language=JavaScript><!--
+OpeningBrowserTags();// --></script>
+
+<!-- saved from url=(0026)http://www.naturaldocs.org -->
+
+<table width=100% border=0 cellspacing=0 cellpadding=0><tr><td colspan=3 class=Header><table width=100% border=0 cellspacing=0 cellpadding=0><tr><td><img src="images/header/leftside.png" width=30 height=75><a href="index.html"><img src="images/header/logo.png" width=524 height=75 alt="Natural Docs"></a></td><td align=right><img src="images/header/rightside.png" width=30 height=75></td></tr></table></td></tr><tr><td><img src="images/header/overleftmargin.png" width=10 height=6></td><td class=SideMenuTop><img src="images/header/overmenu.png" width=14 height=6></td><td class=BodyTop><img src="images/header/overbody.png" width=24 height=6></td></tr><tr><td></td><td class=SideMenu nowrap><div class=SideMenuSection><div class=SideMenuTitle><img src="images/menu/about.png" width=52 height=13 alt="About"></div><div class=SideMenuBody><span class=SideMenuEntry id=SelectedSideMenuEntry>Language Support</span><a href="output.html" class=SideMenuEntry>Output Formats</a></div></div><div class=SideMenuSection><div class=SideMenuTitle><img src="images/menu/using.png" width=45 height=13 alt="Using"></div><div class=SideMenuBody><a href="documenting.html" class=SideMenuEntry>Documenting<br>Your Code</a><a href="keywords.html" class=SideMenuEntry>Keywords</a><a href="running.html" class=SideMenuEntry>Running</a><a href="troubleshooting.html" class=SideMenuEntry>Troubleshooting</a></div></div><div class=SideMenuSection><div class=SideMenuTitle><img src="images/menu/customizing.png" width=96 height=13 alt="Customizing"></div><div class=SideMenuBody><a href="menu.html" class=SideMenuEntry>Organizing the Menu</a><a href="styles.html" class=SideMenuEntry>CSS Styles</a><a href="customizingtopics.html" class=SideMenuEntry>Topics and Keywords</a><a href="customizinglanguages.html" class=SideMenuEntry>Languages, Indexes,<br>and Prototypes</a></div></div><div class=SideMenuSection><div class=SideMenuTitle><img src="images/menu/community.png" width=86 height=13 alt="Community"></div><div class=SideMenuBody><a href="http://www.naturaldocs.org/" class=SideMenuEntry>Web Site</a><a href="http://www.naturaldocs.org/mailinglist.html" class=SideMenuEntry>Mailing Lists</a><a href="http://www.naturaldocs.org/messageboards.html" class=SideMenuEntry>Message Boards</a><a href="http://www.naturaldocs.org/bugs.html" class=SideMenuEntry>Bugs and<br>Feature Requests</a></div></div></td><td class=Body width=100%><div class=PageTitle>Language Support</div><div class=TOC><a href="#FullLanguageSupport">Full Language Support</a> &middot; <a href="#BasicLanguageSupport">Basic Language Support</a></div><div class=Topic><a name=FullLanguageSupport></a><div class=TopicTitle>Full Language Support</div><p>The following languages have full language support, which means you get:</p><p><b>Full code documentation.</b>&nbsp; All functions, variables, and classes will appear in the output regardless of whether you wrote anything for them.&nbsp; This can be turned off with the <a href="running.html#CommandLine"><code>-do</code> command line option.</a></p><p><b>Inheritance diagrams.</b>&nbsp; They will appear in the output wherever appropriate.</p><p><b>Javadoc compatibility.</b>&nbsp; Natural Docs can read most Javadoc comments and include them in the output.&nbsp; You can also write Natural Docs documentation without topic lines by using the Javadoc comment symbols.</p><p><b>Auto-scoping.</b>&nbsp; The class a topic is part of is determined by the source code rather than class and section topics.</p><ul class=LanguageList><li>C# <i>(1.1, some 2.0)</i></li><li>Perl</li><li>ActionScript <i>(2 and 3)</i></li></ul></div><div class=Topic><a name=BasicLanguageSupport></a><div class=TopicTitle>Basic Language Support</div><p>The following languages have basic language support, which means you have:</p><p><b>Explicit documentation only.</b>&nbsp; Only things you write Natural Docs documentation for will appear in the output.</p><p><b>No inheritance diagrams.</b></p><p><b>Natural Docs comments only.</b>&nbsp; They also need to include a topic line.</p><p><b>Topic scoping.</b>&nbsp; The class a topic is part of is determined by the <a href="documenting/reference.html#KeywordsTopicsAndScope">topic scoping rules</a>.</p><ul class=LanguageList><li>C/C++</li><li>Java</li><li>PHP</li><li>Python</li><li>PL/SQL</li><li>Visual Basic</li><li>Pascal/Delphi</li><li>Ada</li><li>JavaScript</li><li>Ruby</li><li>Tcl</li><li>ColdFusion</li><li>Assembly</li><li>Fortran <i>(free-format only)</i></li><li>R</li><li>Makefiles</li><li>Plain Text</li><li><a href="customizinglanguages.html">Custom Languages</a></li></ul></div></td></tr><tr><td></td><td class=SideMenuBottom><table width=100% border=0 cellspacing=0 cellpadding=0><tr><td class=SideMenuBottomLeft><img src="images/menu/bottomleft.png" width=18 height=19></td><td class=SideMenuBottomRight><img src="images/menu/bottomright.png" width=18 height=19></td></tr></table></td><td class=BodyBottom>Copyright &copy; 2003-2008 Greg Valure</td></tr></table><script language=JavaScript><!--
+ClosingBrowserTags();// --></script></body></html>
\ No newline at end of file

Added: trunk/build/NaturalDocs-1.4/Help/menu.html
===================================================================
--- trunk/build/NaturalDocs-1.4/Help/menu.html	                        (rev 0)
+++ trunk/build/NaturalDocs-1.4/Help/menu.html	2009-07-01 15:30:00 UTC (rev 4275)
@@ -0,0 +1,79 @@
+
+
+<html><head><title>Organizing the Menu - Natural Docs</title><link rel=stylesheet type="text/css" href="styles.css"><link rel=stylesheet type="text/css" href="examples.css"><style type="text/css"><!--
+
+
+		.TimestampTable {
+			margin: 1em 4ex;
+			}
+		.TimestampTable td {
+			padding: 0 3ex 0 0;
+			vertical-align: bottom;
+			}
+
+	
+--></style><script language=JavaScript src="javascript/PNGHandling.js"></script><script language=JavaScript src="javascript/BrowserStyles.js"></script><script language=JavaScript src="example/NaturalDocs.js"></script></head><body marginwidth=0 marginheight=0 leftmargin=0 topmargin=0><script language=JavaScript><!--
+OpeningBrowserTags();// --></script>
+
+<!-- saved from url=(0026)http://www.naturaldocs.org -->
+
+<table width=100% border=0 cellspacing=0 cellpadding=0><tr><td colspan=3 class=Header><table width=100% border=0 cellspacing=0 cellpadding=0><tr><td><img src="images/header/leftside.png" width=30 height=75><a href="index.html"><img src="images/header/logo.png" width=524 height=75 alt="Natural Docs"></a></td><td align=right><img src="images/header/rightside.png" width=30 height=75></td></tr></table></td></tr><tr><td><img src="images/header/overleftmargin.png" width=10 height=6></td><td class=SideMenuTop><img src="images/header/overmenu.png" width=14 height=6></td><td class=BodyTop><img src="images/header/overbody.png" width=24 height=6></td></tr><tr><td></td><td class=SideMenu nowrap><div class=SideMenuSection><div class=SideMenuTitle><img src="images/menu/about.png" width=52 height=13 alt="About"></div><div class=SideMenuBody><a href="languages.html" class=SideMenuEntry>Language Support</a><a href="output.html" class=SideMenuEntry>Output Formats</a></div></div><div class=SideMenuSection><div class=SideMenuTitle><img src="images/menu/using.png" width=45 height=13 alt="Using"></div><div class=SideMenuBody><a href="documenting.html" class=SideMenuEntry>Documenting<br>Your Code</a><a href="keywords.html" class=SideMenuEntry>Keywords</a><a href="running.html" class=SideMenuEntry>Running</a><a href="troubleshooting.html" class=SideMenuEntry>Troubleshooting</a></div></div><div class=SideMenuSection><div class=SideMenuTitle><img src="images/menu/customizing.png" width=96 height=13 alt="Customizing"></div><div class=SideMenuBody><span class=SideMenuEntry id=SelectedSideMenuEntry>Organizing the Menu</span><a href="styles.html" class=SideMenuEntry>CSS Styles</a><a href="customizingtopics.html" class=SideMenuEntry>Topics and Keywords</a><a href="customizinglanguages.html" class=SideMenuEntry>Languages, Indexes,<br>and Prototypes</a></div></div><div class=SideMenuSection><div class=SideMenuTitle><img src="images/menu/community.png" width=86 height=13 alt="Community"></div><div class=SideMenuBody><a href="http://www.naturaldocs.org/" class=SideMenuEntry>Web Site</a><a href="http://www.naturaldocs.org/mailinglist.html" class=SideMenuEntry>Mailing Lists</a><a href="http://www.naturaldocs.org/messageboards.html" class=SideMenuEntry>Message Boards</a><a href="http://www.naturaldocs.org/bugs.html" class=SideMenuEntry>Bugs and<br>Feature Requests</a></div></div></td><td class=Body width=100%><div class=PageTitle>Organizing the Menu</div><div class=TOC><a href="#OrderAndTitles">Order and Titles</a> &middot; <a href="#Grouping">Grouping</a> &middot; <a href="#IndexesAndSearch">Indexes and Search</a> &middot; <a href="#AutomaticChanges">Automatic Changes</a><br><a href="#Extras">Extras</a> &middot; <a href="#Errors">Errors</a> &middot; <a href="#PortabilityAndVersioningSystems">Portability and Versioning Systems</a></div><div class=Topic><p>Natural Docs creates a file called <code>Menu.txt</code> in your <a href="running.html#CommandLine">project directory</a> that you can edit to organize the menu.&nbsp; It normally takes care of this on its own, but you have the option of improving it manually if you want to.</p></div><div class=Topic><a name="OrderAndTitles"></a><div class=TopicTitle>Order and Titles</div><p>If you&rsquo;ve never looked in it before, the menu file will have some comments explaining its syntax and a list like you see below.</p><pre class=Example>File: ClassA  (ClassA.h)
+File: ClassB  (ClassB.h)
+File: Globals  (Globals.h)
+</pre><p>The list gets turned into a menu that looks like this:</p><table class=NDMenu><tr><td><div class=MEntry><div class=MFile><a href="#" onClick="return false;">ClassA</a></div></div><div class=MEntry><div class=MFile><a href="#" onClick="return false;">ClassB</a></div></div><div class=MEntry><div class=MFile><a href="#" onClick="return false;">Globals</a></div></div></td></tr></table><p>The lines are in the format &ldquo;<code>File: <i>[title]</i> (<i>[filename]</i>)</code>&rdquo;.&nbsp; When Natural Docs made the menu, it decided on its own what the title of each file should be and then put them in alphabetical order.&nbsp; However, suppose we don&rsquo;t want this.&nbsp; We want Globals above the classes and we want spaces in the menu titles.&nbsp; So we edit the file.</p><pre class=Example>File: Globals  (Globals.h)
+File: Class A  (ClassA.h)
+File: Class B  (ClassB.h)
+</pre><p>Run Natural Docs again and the menu is updated.</p><table class=NDMenu><tr><td><div class=MEntry><div class=MFile><a href="#" onClick="return false;">Globals</a></div></div><div class=MEntry><div class=MFile><a href="#" onClick="return false;">Class A</a></div></div><div class=MEntry><div class=MFile><a href="#" onClick="return false;">Class B</a></div></div></td></tr></table><p>However, open the menu file again and you&rsquo;ll see something interesting.</p><pre class=Example>File: Globals  (Globals.h)
+File: Class A  (no auto-title, ClassA.h)
+File: Class B  (no auto-title, ClassB.h)
+</pre><p>Natural Docs noticed that you changed a couple of the titles and added a <code>no auto-title</code> attribute to each one.&nbsp; This tells it to never change them on it&rsquo;s own in the future, so your changes won&rsquo;t be lost.&nbsp; You don&rsquo;t have to worry about adding this, Natural Docs will always do it automatically.&nbsp; However, to go back to automatic titles you&rsquo;d have to manually remove it.</p></div><div class=Topic><a name="Grouping"></a><div class=TopicTitle>Grouping</div><p>This menu is good for our example, but in the real world they get much, much longer.&nbsp; We can add groups to organize it further.&nbsp; Natural Docs will create them automatically based on the each file&rsquo;s directory, but once again you can improve it manually if that&rsquo;s not good enough.</p><p>You can add groups as shown below.</p><pre class=Example>File: Globals  (Globals.h)
+Group: Classes {
+   File: Class A  (no auto-title, ClassA.h)
+   File: Class B  (no auto-title, ClassB.h) }
+</pre><table class=NDMenu><tr><td><div class=MEntry><div class=MFile><a href="#" onClick="return false;">Globals</a></div></div><div class=MEntry><div class=MGroup><a href="javascript:ToggleMenu('MenuGroup11');">Classes</a><div class=MGroupContent id=MenuGroup11><div class=MEntry><div class=MFile><a href="#" onClick="return false;">Class A</a></div></div><div class=MEntry><div class=MFile><a href="#" onClick="return false;">Class B</a></div></div></div></div></div></td></tr></table><p>You can also nest them inside each other.</p><pre class=Example>File: Globals  (Globals.h)
+Group: Classes {
+   File: Class A  (no auto-title, ClassA.h)
+   Group: Nested Group {
+      File: Class B  (no auto-title, ClassB.h)  }
+   }
+</pre><table class=NDMenu><tr><td><div class=MEntry><div class=MFile><a href="#" onClick="return false;">Globals</a></div></div><div class=MEntry><div class=MGroup><a href="javascript:ToggleMenu('MenuGroup21');">Classes</a><div class=MGroupContent id=MenuGroup21><div class=MEntry><div class=MFile><a href="#" onClick="return false;">Class A</a></div></div><div class=MEntry><div class=MGroup><a href="javascript:ToggleMenu('MenuGroup22');">Nested Group</a><div class=MGroupContent id=MenuGroup22><div class=MEntry><div class=MFile><a href="#" onClick="return false;">Class B</a></div></div></div></div></div></div></div></div></td></tr></table><p>We&rsquo;ll get rid of the nested group because it doesn&rsquo;t make sense for our example.&nbsp; Run Natural Docs, open up the menu file again and take a look.</p><pre class=Example>File: Globals  (Globals.h)
+
+Group: Classes  {
+
+   File: Class A  (no auto-title, ClassA.h)
+   File: Class B  (no auto-title, ClassB.h)
+   }  # Group: Classes
+</pre><p>Natural Docs reformatted it.&nbsp; When you&rsquo;re organizing the menu, you don&rsquo;t have to worry about the indentation or otherwise keeping it neat.&nbsp; The file is reformatted every time it changes, so you can make quick and dirty edits and Natural Docs will keep it readable.</p><p>Besides breaking up long lists, groups also serve another purpose.&nbsp; Clicking on them will make it expand and collapse.&nbsp; Go ahead and try it in the examples above.&nbsp; When the menu gets too long its groups will start being collapsed by default, allowing easier navigation on large projects where it would just be impractical to show everything at once.</p></div><div class=Topic><a name="IndexesAndSearch"></a><div class=TopicTitle>Indexes and Search</div><p>Natural Docs will automatically determine what indexes your project needs and add them to the menu.&nbsp; Anything indexed will also be used for the search feature.&nbsp; The entries will look like this:</p><pre class=Example>Group: Index {
+
+   Index: Everything
+   Class Index: Classes
+   Function Index: Functions
+   }  # Group: Index
+</pre><p>Like the file entries we saw before, you can rename them by editing the title and reorder them by cutting and pasting.&nbsp; However, if you decide you don&rsquo;t want a particular index to be generated, just delete its entry and it will go away.&nbsp; Just like before, Natural Docs will detect this and add something new:</p><pre class=Example>Don't Index: Functions
+</pre><p>As with <code>no auto-title</code>, Natural Docs adds this automatically to make sure it doesn&rsquo;t later undo your changes.</p></div><div class=Topic><a name="AutomaticChanges"></a><div class=TopicTitle>Automatic Changes</div><p>Natural Docs tries to manage the menu on its own as much as possible so you don&rsquo;t have to worry about it.&nbsp; This is just a peek into some of the things it does so you know what to expect.</p><p>You already saw that by default Natural Docs tries to guess what title should be for each file.&nbsp; If you leave it this way, Natural Docs will always update the menu for you if the file&rsquo;s content changes significantly enough to change its guess, such as if you rename the first class defined in it.&nbsp; If you&rsquo;d like to take advantage of this to define the menu title in each source file instead of in the menu itself, add a &ldquo;<code>Title: [title]</code>&rdquo; comment to the top of the file.</p><p>When you add and delete source files, Natural Docs will automatically add and remove them from the menu file.&nbsp; When adding one it will look for the best group to put it in by directory.&nbsp; If your grouping mirrors the source tree somewhat, this will be a lot more accurate.&nbsp; Also, if the group it&rsquo;s putting it in is alphabetized, Natural Docs will put it in the correct place to maintain that alphabetization.&nbsp; In fact, even if an existing file&rsquo;s automatic title changes, it will change it&rsquo;s position to make sure a previously alphabetized group stays that way.</p><p>There are exceptions in alphabetization for the indexes.&nbsp; If a group only contains indexes, it can be the last item on the menu or in its parent group without making it count as unsorted.&nbsp; Also, within groups that only contain indexes, the general index can be first, also without making the group count as unsorted.</p><p>Finally, if Natural Docs adds some files to a group that causes it to become too long, it will attempt to sub-group it based on directory.&nbsp; However, it will <i>only</i> do this when its adding files on its own, so you don&rsquo;t have to worry about it constantly messing up your groups.&nbsp; Since new files aren&rsquo;t added to a project that often, if you change the menu manually it should stay that way for quite some time.</p></div><div class=Topic><a name="Extras"></a><div class=TopicTitle>Extras</div><p>There&rsquo;s more you can do with the menu than just renaming and reorganizing its entries.&nbsp; Natural Docs has a few extras you can add to it as well.</p><a name="TitleAndSubtitle"></a><div class="SubTopic">Title and Subtitle</div><p>You can add a title and subtitle to your menu.</p><pre class=Example>Title: My Project
+SubTitle: Something That Does Something
+
+File: Globals  (Globals.h)
+Group: Classes
+   File: Class A  (no auto-title, ClassA.h)
+   File: Class B  (no auto-title, ClassB.h)
+</pre><table class=NDMenu><tr><td><div class=MTitle>My Project<div class=MSubTitle>Something That Does Something</div></div><div class=MEntry><div class=MFile><a href="#" onClick="return false;">Globals</a></div></div><div class=MEntry><div class=MGroup><a href="javascript:ToggleMenu('MenuGroup31');">Classes</a><div class=MGroupContent id=MenuGroup31><div class=MEntry><div class=MFile><a href="#" onClick="return false;">Class A</a></div></div><div class=MEntry><div class=MFile><a href="#" onClick="return false;">Class B</a></div></div></div></div></div></td></tr></table><p>In addition to adding the title to the menu, the Title tag will also change the HTML page titles from &ldquo;<i>Class A</i>&rdquo; to &ldquo;<i>Class A - My Project</i>&rdquo;, making bookmarks clearer.</p><a name="TextAndWebLinks"></a><div class="SubTopic">Text and Web Links</div><p>You can also add arbitrary text and web links to your menu.</p><pre class=Example>File: Globals  (Globals.h)
+Group: Classes  {
+   Text: I couldn't think of good names for these classes.
+   File: Class A  (no auto-title, ClassA.h)
+   File: Class B  (no auto-title, ClassB.h)
+   }
+Link: Built with Natural Docs  (http://www.naturaldocs.org)
+</pre><table class=NDMenu><tr><td><div class=MEntry><div class=MFile><a href="#" onClick="return false;">Globals</a></div></div><div class=MEntry><div class=MGroup><a href="javascript:ToggleMenu('MenuGroup51');">Classes</a><div class=MGroupContent id=MenuGroup51><div class=MEntry><div class=MText>I couldn&rsquo;t think of good names for these classes.</div></div><div class=MEntry><div class=MFile><a href="#" onClick="return false;">Class A</a></div></div><div class=MEntry><div class=MFile><a href="#" onClick="return false;">Class B</a></div></div></div></div></div><div class=MEntry><div class=MLink><a href="#" onClick="return false;">Built with Natural Docs</a></div></div></td></tr></table><p>Even though comments use the # character, adding an anchor to a link (such as &ldquo;http://www.website.com/page.html#anchor&rdquo;) will still work.</p><a name="Footers"></a><div class="SubTopic">Footers</div><p>Finally, you can add a footer to all your pages, such as a copyright notice.&nbsp; Natural Docs will change any (c)&rsquo;s it finds into real copyright symbols.</p><pre class=Example>Footer: Copyright (C) 2008 Me
+</pre><table class=NDFooter><tr><td>Copyright &copy; 2008 Me&nbsp; &middot;&nbsp; <a href="http://www.naturaldocs.org">Generated by Natural Docs</a></td></tr></table><p>You can also add a timestamp in any format you want.&nbsp; The tokens you can use in building it are:</p><p><table class=TimestampTable></p><p><tr><td><code>m</code></td><td>One or two digit month.</td><td>January is &ldquo;1&rdquo;</td></tr></p><p><tr><td><code>mm</code></td><td>Always two digit month.</td><td>January is &ldquo;01&rdquo;</td></tr></p><p><tr><td><code>mon</code></td><td>Short month word.</td><td>January is &ldquo;Jan&rdquo;</td></tr></p><p><tr><td><code>month</code></td><td>Long month word.</td><td>January is &ldquo;January&rdquo;</td></tr></p><p><tr><td><code>d</code></td><td>One or two digit day.</td><td>1 is &ldquo;1&rdquo;</td></tr></p><p><tr><td><code>dd</code></td><td>Always two digit day.</td><td>1 is &ldquo;01&rdquo;</td></tr></p><p><tr><td><code>day</code></td><td>Day with letter extension.</td><td>1 is &ldquo;1st&rdquo;</td></tr></p><p><tr><td><code>yy</code></td><td>Two digit year.</td><td>2008 is &ldquo;08&rdquo;</td></tr></p><p><tr><td><code>yyyy</code></td><td>Four digit year.</td><td>2008 is &ldquo;2008&rdquo;</td></tr></p><p><tr><td><code>year</code></td><td>Four digit year.</td><td>2008 is &ldquo;2008&rdquo;</td></tr></p><p></table></p><p>Everything else appears literally, so we can add:</p><pre class=Example>Timestamp: Updated month day, year
+</pre><p>and get:</p><table class=NDFooter><tr><td>Copyright &copy; 2008 Me&nbsp; &middot;&nbsp; Updated January 1st, 2008&nbsp; &middot;&nbsp; <a href="http://www.naturaldocs.org">Generated by Natural Docs</a></td></tr></table></div><div class=Topic><a name="Errors"></a><div class=TopicTitle>Errors</div><p>If there&rsquo;s ever an error in the menu file, Natural Docs will tell you when it&rsquo;s run.&nbsp; It also adds a comment for each one in the menu file itself so that you can search for them in a text editor.</p><pre class=Example># There is an error in this file.  Search for ERROR to find it.
+
+File: Globals  (Globals.h)
+Group: Classes  {
+# ERROR: Txet is not a valid keyword.
+   Txet: I couldn't think of good names for these classes.
+   File: Class A  (no auto-title, ClassA.h)
+   File: Class B  (no auto-title, ClassB.h)
+   }
+</pre><p>Remember that Natural Docs reformats the menu file whenever it&rsquo;s run, so you only need to correct the error.&nbsp; Natural Docs will remove the error comments on its own.</p></div><div class=Topic><a name="PortabilityAndVersioningSystems"></a><div class=TopicTitle>Portability and Versioning Systems</div><p>If you only use <a href="running.html">one input directory</a>, all the files in the menu will have relative paths.&nbsp; However, if you have more Natural Docs will use the absolute path instead.</p><p>This is not a problem.&nbsp; The menu file can still be shared between machines even if they don&rsquo;t keep the source tree in the exact same location.&nbsp; As long as you have the same layout within the source tree and point to the same base directories in the command line, Natural Docs will be able to convert the paths automatically for the new machine.</p><p>However, if you&rsquo;re putting the menu file in a versioning system like Subversion or SourceSafe, it might be very desirable to only have relative paths so anybody can check it in and only the real changes show.&nbsp; In that case, instead of using multiple input directories, see if it&rsquo;s possible to only have one input directory and use the <a href="running.html#CommandLine"><code>-xi</code> command line option</a> to exclude the subdirectories you don&rsquo;t want scanned.</p></div><div class=Topic><a name="ThatsIt"></a><div class=TopicTitle>That&rsquo;s It!</div><p>And we&rsquo;re done.&nbsp; The syntax to do all of this is included in the menu file itself, so you don&rsquo;t need to memorize everything.&nbsp; You shouldn&rsquo;t need to organize the menu very often, just after a lot of new files have been added and if you don&rsquo;t like the default.</p><p>Note that if you&rsquo;re using the non-framed HTML output format, changing the menu does require every output file to be updated.&nbsp; However, Natural Docs has a special process just for this so it won&rsquo;t take nearly as long as if it were rebuilding them all from scratch.&nbsp; Still, if you&rsquo;re working on a large project, it may be worth considering the framed HTML output format.</p></div></td></tr><tr><td></td><td class=SideMenuBottom><table width=100% border=0 cellspacing=0 cellpadding=0><tr><td class=SideMenuBottomLeft><img src="images/menu/bottomleft.png" width=18 height=19></td><td class=SideMenuBottomRight><img src="images/menu/bottomright.png" width=18 height=19></td></tr></table></td><td class=BodyBottom>Copyright &copy; 2003-2008 Greg Valure</td></tr></table><script language=JavaScript><!--
+ClosingBrowserTags();// --></script></body></html>
\ No newline at end of file

Added: trunk/build/NaturalDocs-1.4/Help/output.html
===================================================================
--- trunk/build/NaturalDocs-1.4/Help/output.html	                        (rev 0)
+++ trunk/build/NaturalDocs-1.4/Help/output.html	2009-07-01 15:30:00 UTC (rev 4275)
@@ -0,0 +1,84 @@
+
+
+<html><head><title>Output Formats - Natural Docs</title><link rel=stylesheet type="text/css" href="styles.css"><style type="text/css"><!--
+
+
+        .FormatTable {
+            margin-top: 1em;
+            }
+
+        .FormatTable td {
+            padding-bottom: .5em;
+            line-height: 150%;
+            }
+
+        .FormatName {
+            font: bold 12pt Georgia, serif;
+            padding-left: 5ex;
+            }
+
+        .FormatExample
+            {
+            padding-left: 3ex;
+            }
+
+        .FormatDescription {
+            width: 100%;
+            padding-left: 3ex;
+            padding-right: 5ex;
+            }
+
+
+        .BrowserTable {
+            margin-top: 1em;
+            }
+
+        .BrowserTable td {
+            padding-bottom: .5em;
+            line-height: 150%;
+            }
+
+        .BrowserName {
+            font: bold 12pt Georgia, serif;
+            padding-left: 5ex;
+            }
+        .BrowserSubNames {
+            font: italic 8pt Georgia, serif;
+            }
+
+        .BrowserVersion {
+            padding-left: 3ex;
+            }
+
+        .BrowserDescription {
+            width: 100%;
+            line-height: 150%;
+            padding-left: 3ex;
+            padding-right: 5ex;
+            }
+
+        .FormatExample,
+        .FormatDescription,
+        .BrowserVersion,
+        .BrowserDescription {
+            padding-top: 4px;
+            }
+        .IE .FormatExample,
+        .IE .FormatDescription,
+        .IE .BrowserVersion,
+        .IE .BrowserDescription {
+            padding-top: 3px;
+            }
+        .IE .FormatTable,
+        .IE .BrowserTable {
+            }
+
+
+    
+--></style><script language=JavaScript src="javascript/PNGHandling.js"></script><script language=JavaScript src="javascript/BrowserStyles.js"></script></head><body marginwidth=0 marginheight=0 leftmargin=0 topmargin=0><script language=JavaScript><!--
+OpeningBrowserTags();// --></script>
+
+<!-- saved from url=(0026)http://www.naturaldocs.org -->
+
+<table width=100% border=0 cellspacing=0 cellpadding=0><tr><td colspan=3 class=Header><table width=100% border=0 cellspacing=0 cellpadding=0><tr><td><img src="images/header/leftside.png" width=30 height=75><a href="index.html"><img src="images/header/logo.png" width=524 height=75 alt="Natural Docs"></a></td><td align=right><img src="images/header/rightside.png" width=30 height=75></td></tr></table></td></tr><tr><td><img src="images/header/overleftmargin.png" width=10 height=6></td><td class=SideMenuTop><img src="images/header/overmenu.png" width=14 height=6></td><td class=BodyTop><img src="images/header/overbody.png" width=24 height=6></td></tr><tr><td></td><td class=SideMenu nowrap><div class=SideMenuSection><div class=SideMenuTitle><img src="images/menu/about.png" width=52 height=13 alt="About"></div><div class=SideMenuBody><a href="languages.html" class=SideMenuEntry>Language Support</a><span class=SideMenuEntry id=SelectedSideMenuEntry>Output Formats</span></div></div><div class=SideMenuSection><div class=SideMenuTitle><img src="images/menu/using.png" width=45 height=13 alt="Using"></div><div class=SideMenuBody><a href="documenting.html" class=SideMenuEntry>Documenting<br>Your Code</a><a href="keywords.html" class=SideMenuEntry>Keywords</a><a href="running.html" class=SideMenuEntry>Running</a><a href="troubleshooting.html" class=SideMenuEntry>Troubleshooting</a></div></div><div class=SideMenuSection><div class=SideMenuTitle><img src="images/menu/customizing.png" width=96 height=13 alt="Customizing"></div><div class=SideMenuBody><a href="menu.html" class=SideMenuEntry>Organizing the Menu</a><a href="styles.html" class=SideMenuEntry>CSS Styles</a><a href="customizingtopics.html" class=SideMenuEntry>Topics and Keywords</a><a href="customizinglanguages.html" class=SideMenuEntry>Languages, Indexes,<br>and Prototypes</a></div></div><div class=SideMenuSection><div class=SideMenuTitle><img src="images/menu/community.png" width=86 height=13 alt="Community"></div><div class=SideMenuBody><a href="http://www.naturaldocs.org/" class=SideMenuEntry>Web Site</a><a href="http://www.naturaldocs.org/mailinglist.html" class=SideMenuEntry>Mailing Lists</a><a href="http://www.naturaldocs.org/messageboards.html" class=SideMenuEntry>Message Boards</a><a href="http://www.naturaldocs.org/bugs.html" class=SideMenuEntry>Bugs and<br>Feature Requests</a></div></div></td><td class=Body width=100%><div class=PageTitle>Output Formats</div><div class=Topic><p>These are the output formats that are currently implemented in Natural Docs.</p><table width=100% border=0 cellspacing=0 cellpadding=0 class=FormatTable><tr><td class=FormatName>HTML</td><td class=FormatDescription>HTML output.&nbsp; Each page is self-contained.&nbsp; Linking to specific pages is easy, but every file has to be updated whenever the menu changes.</td></tr><tr><td class=FormatName>FramedHTML</td><td class=FormatDescription>HTML output based on frames.&nbsp; The menu is updated quickly, but linking to individual pages is difficult and some people just plain hate frames.</td></tr></table></div><div class=Topic><div class=TopicTitle>HTML Compatibility</div><p>These are the browsers Natural Docs&rsquo; HTML output has been tested with.&nbsp; All browsers will be able to view and navigate it, some of the older ones just may not be able to use advanced features like search correctly.</p><table width=100% border=0 cellspacing=0 cellpadding=0 class=BrowserTable><tr><td class=BrowserName nowrap>FireFox</td><td class=BrowserDescription>Tested with 1.0, 1.5, and 2.0.</td></tr><tr><td class=BrowserName nowrap>Internet Explorer</td><td class=BrowserDescription>Tested with 6 and 7.</td></tr><tr><td class=BrowserName nowrap>Safari</td><td class=BrowserDescription>Tested with 2 and 3.&nbsp; Search doesn&rsquo;t work with unframed HTML in 2.</td></tr><tr><td class=BrowserName nowrap>Opera</td><td class=BrowserDescription>Tested with 7.0, 7.5, 8.0, 8.5, and 9.0.&nbsp; Search doesn&rsquo;t work with 7.0, and sometimes tooltips.</td></tr><tr><td class=BrowserName nowrap>Konqueror</td><td class=BrowserDescription>Tested with 3.5.5.&nbsp; Search doesn&rsquo;t work.</td></tr></table></div></td></tr><tr><td></td><td class=SideMenuBottom><table width=100% border=0 cellspacing=0 cellpadding=0><tr><td class=SideMenuBottomLeft><img src="images/menu/bottomleft.png" width=18 height=19></td><td class=SideMenuBottomRight><img src="images/menu/bottomright.png" width=18 height=19></td></tr></table></td><td class=BodyBottom>Copyright &copy; 2003-2008 Greg Valure</td></tr></table><script language=JavaScript><!--
+ClosingBrowserTags();// --></script></body></html>
\ No newline at end of file

Added: trunk/build/NaturalDocs-1.4/Help/running.html
===================================================================
--- trunk/build/NaturalDocs-1.4/Help/running.html	                        (rev 0)
+++ trunk/build/NaturalDocs-1.4/Help/running.html	2009-07-01 15:30:00 UTC (rev 4275)
@@ -0,0 +1,40 @@
+
+
+<html><head><title>Running Natural Docs</title><link rel=stylesheet type="text/css" href="styles.css"><style type="text/css"><!--
+
+
+        .OptionTable        { margin: 1em 3ex 0 3ex }
+        .OptionTable td        { padding-bottom: 1em }
+
+        .Option                { font: 10pt Courier New, Courier, monospace; color: #808080; white-space: nowrap }
+        .Description        { padding-left: 4ex }
+        .Description ul { margin: 0 0 0 5ex; padding: 0 }
+
+        .ParameterGroup {
+            font: bold 10pt Verdana, sans-serif;
+            padding-top: 1em;
+            }
+        .ParameterGroupExtra {
+            font: italic 8pt Verdana, sans-serif;
+            color: #808080;
+            }
+
+    
+--></style><script language=JavaScript src="javascript/PNGHandling.js"></script><script language=JavaScript src="javascript/BrowserStyles.js"></script></head><body marginwidth=0 marginheight=0 leftmargin=0 topmargin=0><script language=JavaScript><!--
+OpeningBrowserTags();// --></script>
+
+<!-- saved from url=(0026)http://www.naturaldocs.org -->
+
+<table width=100% border=0 cellspacing=0 cellpadding=0><tr><td colspan=3 class=Header><table width=100% border=0 cellspacing=0 cellpadding=0><tr><td><img src="images/header/leftside.png" width=30 height=75><a href="index.html"><img src="images/header/logo.png" width=524 height=75 alt="Natural Docs"></a></td><td align=right><img src="images/header/rightside.png" width=30 height=75></td></tr></table></td></tr><tr><td><img src="images/header/overleftmargin.png" width=10 height=6></td><td class=SideMenuTop><img src="images/header/overmenu.png" width=14 height=6></td><td class=BodyTop><img src="images/header/overbody.png" width=24 height=6></td></tr><tr><td></td><td class=SideMenu nowrap><div class=SideMenuSection><div class=SideMenuTitle><img src="images/menu/about.png" width=52 height=13 alt="About"></div><div class=SideMenuBody><a href="languages.html" class=SideMenuEntry>Language Support</a><a href="output.html" class=SideMenuEntry>Output Formats</a></div></div><div class=SideMenuSection><div class=SideMenuTitle><img src="images/menu/using.png" width=45 height=13 alt="Using"></div><div class=SideMenuBody><a href="documenting.html" class=SideMenuEntry>Documenting<br>Your Code</a><a href="keywords.html" class=SideMenuEntry>Keywords</a><span class=SideMenuEntry id=SelectedSideMenuEntry>Running</span><a href="troubleshooting.html" class=SideMenuEntry>Troubleshooting</a></div></div><div class=SideMenuSection><div class=SideMenuTitle><img src="images/menu/customizing.png" width=96 height=13 alt="Customizing"></div><div class=SideMenuBody><a href="menu.html" class=SideMenuEntry>Organizing the Menu</a><a href="styles.html" class=SideMenuEntry>CSS Styles</a><a href="customizingtopics.html" class=SideMenuEntry>Topics and Keywords</a><a href="customizinglanguages.html" class=SideMenuEntry>Languages, Indexes,<br>and Prototypes</a></div></div><div class=SideMenuSection><div class=SideMenuTitle><img src="images/menu/community.png" width=86 height=13 alt="Community"></div><div class=SideMenuBody><a href="http://www.naturaldocs.org/" class=SideMenuEntry>Web Site</a><a href="http://www.naturaldocs.org/mailinglist.html" class=SideMenuEntry>Mailing Lists</a><a href="http://www.naturaldocs.org/messageboards.html" class=SideMenuEntry>Message Boards</a><a href="http://www.naturaldocs.org/bugs.html" class=SideMenuEntry>Bugs and<br>Feature Requests</a></div></div></td><td class=Body width=100%><div class=PageTitle>Running Natural Docs</div><div class=TOC><a href="#HowAndWhen">How and When</a> &middot; <a href="#CommandLine">Command Line</a> &middot; <a href="#Example">Examples</a></div></div><div class=Topic><a name=HowAndWhen></a>&nbsp; <div class=TopicTitle>How and When</div><p>Probably the best way to run Natural Docs is as part of the build process.&nbsp; This way every time you compile your code, your documentation is updated as well and you always have a current reference.&nbsp; Natural Docs has a differential build process so it will not rebuild the entire set of documentation every time it&rsquo;s run.</p><p>If you&rsquo;d like to run it manually instead, you should determine the command line you need and save it as a shortcut, batch file, or script since you should be running it often and will rarely need to fiddle with the parameters.</p><p></p></div><div class=Topic><a name=CommandLine></a><div class=TopicTitle>Command Line</div><pre>NaturalDocs -i [input (source) directory]
+            -o [output format] [output directory]
+            -p [project directory]
+            [options]</pre><table class=OptionTable border=0 cellspacing=0 cellpadding=0><tr><td colspan=2 class="First ParameterGroup">Required Parameters:</td></tr><tr><td class=Option>-i <i>[dir]</i><br>--input <i>[dir]</i><br>--source <i>[dir]</i></td><td class=Description><p>The input (source) directory.&nbsp; Natural Docs will build the documentation from the files in this directory and all its subdirectories.&nbsp; You can specify it multiple times to include multiple directories.&nbsp; <a href="languages.html">See the list of supported programming languages.</a></p></tr><tr><td class=Option>-o <i>[fmt] [dir]</i><br>--output <i>[fmt] [dir]</i></td><td class=Description><p>The output format and directory.&nbsp; This can also be specified multiple times, so you can build the documentation in multiple formats in a single run.&nbsp; <a href="output.html">See the list of supported output formats.</a></p></td></tr><tr><td class=Option>-p <i>[dir]</i><br>--project <i>[dir]</i></td><td class=Description><p>The project directory.&nbsp; Natural Docs needs a place to store configuration and data files for each project it&rsquo;s run on, so this is where it will put them.&nbsp; No two projects should share the same directory.</p></td></tr><tr><td colspan=2 class=ParameterGroup>Optional Parameters:</td></tr><tr><td class=Option>-xi <i>[dir]</i><br>--exclude-input <i>[dir]</i><br>--exclude-source <i>[dir]</i></td><td class=Description><p>Excludes a subdirectory from being scanned.&nbsp; The output and project directories are automatically excluded.</p></td></tr><tr><td class=Option>-img <i>[dir]</i><br>--images <i>[dir]</i></td><td class=Description><p>Adds a directory to search for image files when using <a href="documenting/reference.html#Images"><code>(see <i>[file]</i>)</code></a>.</p></td></tr><tr><td class=Option>-s <i>[style] (<i>[style]</i> ...)</i><br>--style <i>[style]</i> (<i>[style]</i> ...)</td><td class=Description><p>Selects the CSS style for HTML output.&nbsp; <a href="styles.html">See the default list of styles.</a></p><p>You can use any CSS file in your project directory or Natural Docs&rsquo; Styles directory just by using its name without the .css extension.&nbsp; If you include more than one, they will all be included in the HTML that order.</p></td></tr><tr><td class=Option>-r<br>--rebuild</td><td class=Description><p>Rebuilds everything from scratch.&nbsp; All source files will be rescanned and all output files will be rebuilt</p></td></tr><tr><td class=Option>-ro<br>--rebuild-output</td><td class=Description><p>Rebuilds all output files from scratch.</p></td></tr><tr><td class=Option>-t <i>[len]</i><br>--tab-length <i>[len]</i></td><td class=Description><p>Sets the number of spaces tabs should be expanded to.&nbsp; This only needs to be set if you use tabs in example code or text diagrams.&nbsp; The default is 4.</p></td></tr><tr><td class=Option>-do<br>--documented-only</td><td class=Description><p>Tells Natural Docs to only include what you explicitly document in the output, and not to find undocumented classes, functions, and variables.&nbsp; This option is only relevant if you have <a href="languages.html">full language support</a>.</p></td></tr><tr><td class=Option>-oft<br>--only-file-titles</td><td class=Description><p>Tells Natural Docs to only use the file name for its menu and page titles.&nbsp; It won&rsquo;t try to determine one from the contents of the file.</p></td></tr><tr><td class=Option>-nag<br>--no-auto-group</td><td class=Description><p>Tells Natural Docs to not automatically create group topics if you don&rsquo;t add them yourself.</p></td></tr><tr><td class=Option>-cs <i>[charset]</i><br>--charset <i>[charset]</i><br>--character-set <i>[charset]</i></td><td class=Description><p>Sets the character set property of the generated HTML, such as UTF-8 or Shift_JIS.&nbsp; The default leaves it unspecified.</p></td></tr><tr><td class=Option>-q<br>--quiet</td><td class=Description><p>Suppresses all non-error output.</p></td></tr><tr><td class=Option>-?<br>-h<br>--help</td><td class=Description><p>Prints the syntax reference.</p></td></tr><tr><td colspan=2 class=ParameterGroup>No Longer Supported:<div class=ParameterGroupExtra>These parameters were part of previous Natural Docs releases, but are no longer supported.</div></td></tr><tr><td class=Option>-ho<br>--headers-only</td><td class=Description><p>This used to check only the headers and not the source files in C and C++.&nbsp; <a href="customizinglanguages.html">Edit <code>Languages.txt</code> instead</a> and add the line <code>&ldquo;Ignore Extensions: c cpp cxx&rdquo;</code>.</p></td></tr><tr><td class=Option>-s Custom<br>--style Custom</td><td class=Description><p>This used to tell Natural Docs not to alter the CSS file in the output directory.&nbsp; Copy your custom CSS file to your project directory and use it with <code>-s</code> instead.</p></td></tr><tr><td class=Option>-ag <i>[level]</i><br>--auto-group <i>[level]</i></td><td class=Description><p>This used to set the level of auto-grouping between Full, Basic, and None.&nbsp; The algorithm was improved so there&rsquo;s no need for separate levels anymore.&nbsp; You can use <code>-nag</code> if you want to turn it off completely.</p></td></tr></table></div><div class=Topic><a name=Examples></a><div class=TopicTitle>Examples</div><pre>NaturalDocs -i C:\My Project\Source
+            -o FramedHTML C:\My Project\Docs
+            -p C:\My Project\Natural Docs
+
+NaturalDocs -i /project/src1
+            -i /project/src2
+            -o HTML /project/doc
+            -p /project/nd
+            -s Small -t 3</pre></div></td></tr><tr><td></td><td class=SideMenuBottom><table width=100% border=0 cellspacing=0 cellpadding=0><tr><td class=SideMenuBottomLeft><img src="images/menu/bottomleft.png" width=18 height=19></td><td class=SideMenuBottomRight><img src="images/menu/bottomright.png" width=18 height=19></td></tr></table></td><td class=BodyBottom>Copyright &copy; 2003-2008 Greg Valure</td></tr></table><script language=JavaScript><!--
+ClosingBrowserTags();// --></script></body></html>
\ No newline at end of file

Added: trunk/build/NaturalDocs-1.4/Help/styles.css
===================================================================
--- trunk/build/NaturalDocs-1.4/Help/styles.css	                        (rev 0)
+++ trunk/build/NaturalDocs-1.4/Help/styles.css	2009-07-01 15:30:00 UTC (rev 4275)
@@ -0,0 +1,290 @@
+
+body {
+    background: #FFFFFF;
+    margin: 18px 15px 25px 15px !important;
+    }
+
+body,
+td,
+li {
+     font: 9pt Verdana, sans-serif;
+     }
+p,
+td,
+li {
+     line-height: 150%;
+    }
+
+p {
+    text-indent: 4ex;
+    margin: 0;
+    }
+
+td {
+    vertical-align: top;
+    }
+
+a:link,
+a:visited { color: #900000; text-decoration: none }
+a:hover { color: #900000; text-decoration: underline }
+a:active { color: #FF0000; text-decoration: underline }
+
+.Opera wbr:after {
+	content: "\00200B";
+	}
+
+.NoIndent p
+    {  text-indent: 0;  }
+
+img {
+    border: none;
+    }
+
+.First {
+    margin-top: 0 !important;
+    padding-top: 0 !important;
+    }
+.Last {
+    margin-bottom: 0 !important;
+    padding-bottom: 0 !important;
+    }
+
+.Header {
+        background-image: URL("images/header/background.png");
+        background-color: #7070C0;
+        }
+.SideMenuTop {
+        background: URL("images/header/overmenubg.png");
+        }
+.SideMenuBottom {
+        vertical-align: bottom;
+        }
+.BodyTop {
+        background: URL("images/header/overbodybg.png");
+        text-align: right;
+        }
+.BodyBottom {
+        vertical-align: bottom;
+        text-align: right;
+        font: italic 8pt Georgia, serif;
+        color: #C0C0C0;
+        padding-right: 10px;
+        }
+
+.Body {
+    padding: 15px 20px 0 25px;
+    }
+
+#SourceForgeLogo {
+	border: 1px solid #D8D8D8;
+	margin-top: 2em;
+	}
+
+
+
+pre, code, .Example {
+    font: 10pt Courier New, Courier, monospace;
+    color: #606060;
+    }
+a code {
+    color: #C06060;
+    }
+.Example {
+    overflow: auto;
+    }
+
+.PageTitle {
+    font: bold italic 21pt Trebuchet MS, sans-serif;  letter-spacing: .5ex; text-transform: uppercase;
+    margin-bottom: .5em }
+.IE .PageTitle {
+    letter-spacing: 1.25ex;
+    }
+
+
+.Topic {
+    margin-bottom: 2em }
+
+
+.TopicTitle {
+    font: 18pt Georgia, serif;
+    border-width: 0 0 1px 0; border-style: solid; border-color: #C0C0C0;
+    margin-bottom: .5em
+    }
+#SubscribeTopicTitle {
+    margin-bottom: 0;
+    }
+.Subscribe {
+    font-size: 8pt;
+    margin-bottom: 2em;
+    color: #909090;
+    }
+
+.Subscribe a:link,
+.Subscribe a:hover,
+.Subscribe a:visited {
+    color: #909090 }
+
+
+.SubTopic {
+        font-weight: bold; font-size: 10pt;
+        padding-top: 1.5em; padding-bottom: .5em;
+        }
+
+.MiniTopic {
+        font-weight: bold;
+        padding-top: 1em; padding-bottom: 0em;
+        }
+
+
+.TOC {
+        text-align: center;
+        font: 8pt Verdana, sans-serif;
+        text-transform: uppercase;
+        background-color: #F8F8F8;
+        border-width: 1px; border-style: solid; border-color: #C0C0C0;
+        margin-bottom: 1.5em;
+        padding: 2px 0;
+        -moz-border-radius: 14px;
+        }
+
+    .TOC a {
+        margin: 0 0.75ex; }
+
+    .TOC a:link,
+    .TOC a:hover,
+    .TOC a:visited {
+        color: #404040 }
+
+
+.Example {
+    background-color: #FDFDFD;
+    padding: 15px;
+    border: 1px solid #C0C0C0;
+    border-width: 1px 1px 1px 6px;
+    border-style: dashed dashed dashed solid;
+    color: #707070;
+    margin: 15px 5ex;
+    }
+
+
+.LastUpdated {
+    font: italic 10pt Georgia, serif;
+    color: #A0A0A0;
+    margin: 1em 0;
+    }
+
+
+
+.FAQSummary {
+    margin-bottom: 3em;
+    }
+.FAQSummaryGroup {
+    font: bold 12pt Georgia, serif;
+    margin: 1em 0 .25em 0;
+    }
+.FAQGroup {
+    font: 18pt Georgia, serif;
+    border-bottom: 1px solid #C0C0C0;
+    margin-bottom: .5em;
+    margin-top: 1.5em;
+    }
+.FAQSummaryEntry:link,
+.FAQSummaryEntry:visited,
+.FAQSummaryEntry:hover,
+.FAQSummaryEntry:active {
+    }
+
+.FAQEntry {
+    margin-bottom: 3em;
+    }
+.FAQEntryTitle {
+    font: bold 12pt Georgia, serif;
+    margin-bottom: .5em;
+    }
+.FAQEntry .SubTopic {
+    font: italic 9pt Verdana, sans-serif;
+    }
+
+
+
+.SideMenu {
+    width: 175px;  /* 195 minus padding */
+    text-align: center;
+    padding-top: 15px;
+    background-color: #F0F0F0;
+    }
+.SideMenuBottom {
+    background-color: #F0F0F0;
+    }
+.SideMenuBottomRight {
+    text-align: right;
+    }
+
+.SideMenuSection {
+    margin-bottom: 3em;
+    }
+
+.SideMenuTitle {
+    padding-bottom: 3px;
+    border-bottom: 1px solid #D0D0D0;
+    }
+
+.SideMenuBody {
+    padding-top: 1em;
+    background: URL("images/menu/background.png") repeat-x;
+    }
+
+.SideMenuEntry {
+    font: 8pt Verdana, sans-serif;
+    margin: 0 10px 1em 10px;
+    display: block;
+    }
+
+a.SideMenuEntry:link,
+a.SideMenuEntry:visited {
+    color: #000000;
+    padding: 1px 10px 2px 9px;
+    }
+a.SideMenuEntry:hover,
+a.SideMenuEntry:active,
+#SelectedSideMenuEntry {
+    border-style: solid;
+    border-width: 1px 2px 2px 1px;
+    padding: 0 8px;
+    text-decoration: none;
+    -moz-border-radius: 10px;
+    }
+a.SideMenuEntry:hover,
+a.SideMenuEntry:active {
+	color: #000000;
+    border-color: #C8C8C8;
+    background-color: #F8F8F8;
+    }
+#SelectedSideMenuEntry {
+	color: #000000;
+    border-color: #606060;
+    background-color: #FFFFFF;
+    }
+
+.SideMenuSourceForge {
+    padding-top: 5px;
+    }
+
+
+
+/* Needed by the release notes for 1.3 */
+
+.ExPrototype {
+    font: 10pt Courier New, Courier, monospace;
+    padding: 5px 3ex;
+    background-color: #F4F4F4;
+    border: 1px solid #D0D0D0;
+    margin: 1em 0;
+    }
+.ExPrototype td {
+    font: 10pt Courier New, Courier, monospace;
+    }
+.ExPrototype .Fade {
+    color: #8F8F8F;
+    }
+

Added: trunk/build/NaturalDocs-1.4/Help/styles.html
===================================================================
--- trunk/build/NaturalDocs-1.4/Help/styles.html	                        (rev 0)
+++ trunk/build/NaturalDocs-1.4/Help/styles.html	2009-07-01 15:30:00 UTC (rev 4275)
@@ -0,0 +1,52 @@
+
+
+<html><head><title>CSS Styles - Natural Docs</title><link rel=stylesheet type="text/css" href="styles.css"><style type="text/css"><!--
+
+
+        .StyleTable {
+            margin: 1em 5ex 0 5ex }
+
+        .StyleTable td {
+            padding-bottom: .5em}
+
+        .StyleName {
+            font: bold 12pt Georgia, serif;
+            }
+
+        .StyleView,
+        .StyleDownload
+            {
+            padding-left: 3ex;
+            }
+
+        .StyleDescription {
+            width: 100%;
+            padding-left: 3ex }
+
+        .StyleView,
+        .StyleDownload,
+        .StyleDescription {
+            padding-top: 1px;
+            }
+        .IE .StyleView,
+        .IE .StyleDownload,
+        .IE .StyleDescription {
+            padding-top: 0;
+            }
+
+    
+--></style><script language=JavaScript src="javascript/PNGHandling.js"></script><script language=JavaScript src="javascript/BrowserStyles.js"></script></head><body marginwidth=0 marginheight=0 leftmargin=0 topmargin=0><script language=JavaScript><!--
+OpeningBrowserTags();// --></script>
+
+<!-- saved from url=(0026)http://www.naturaldocs.org -->
+
+<table width=100% border=0 cellspacing=0 cellpadding=0><tr><td colspan=3 class=Header><table width=100% border=0 cellspacing=0 cellpadding=0><tr><td><img src="images/header/leftside.png" width=30 height=75><a href="index.html"><img src="images/header/logo.png" width=524 height=75 alt="Natural Docs"></a></td><td align=right><img src="images/header/rightside.png" width=30 height=75></td></tr></table></td></tr><tr><td><img src="images/header/overleftmargin.png" width=10 height=6></td><td class=SideMenuTop><img src="images/header/overmenu.png" width=14 height=6></td><td class=BodyTop><img src="images/header/overbody.png" width=24 height=6></td></tr><tr><td></td><td class=SideMenu nowrap><div class=SideMenuSection><div class=SideMenuTitle><img src="images/menu/about.png" width=52 height=13 alt="About"></div><div class=SideMenuBody><a href="languages.html" class=SideMenuEntry>Language Support</a><a href="output.html" class=SideMenuEntry>Output Formats</a></div></div><div class=SideMenuSection><div class=SideMenuTitle><img src="images/menu/using.png" width=45 height=13 alt="Using"></div><div class=SideMenuBody><a href="documenting.html" class=SideMenuEntry>Documenting<br>Your Code</a><a href="keywords.html" class=SideMenuEntry>Keywords</a><a href="running.html" class=SideMenuEntry>Running</a><a href="troubleshooting.html" class=SideMenuEntry>Troubleshooting</a></div></div><div class=SideMenuSection><div class=SideMenuTitle><img src="images/menu/customizing.png" width=96 height=13 alt="Customizing"></div><div class=SideMenuBody><a href="menu.html" class=SideMenuEntry>Organizing the Menu</a><span class=SideMenuEntry id=SelectedSideMenuEntry>CSS Styles</span><a href="customizingtopics.html" class=SideMenuEntry>Topics and Keywords</a><a href="customizinglanguages.html" class=SideMenuEntry>Languages, Indexes,<br>and Prototypes</a></div></div><div class=SideMenuSection><div class=SideMenuTitle><img src="images/menu/community.png" width=86 height=13 alt="Community"></div><div class=SideMenuBody><a href="http://www.naturaldocs.org/" class=SideMenuEntry>Web Site</a><a href="http://www.naturaldocs.org/mailinglist.html" class=SideMenuEntry>Mailing Lists</a><a href="http://www.naturaldocs.org/messageboards.html" class=SideMenuEntry>Message Boards</a><a href="http://www.naturaldocs.org/bugs.html" class=SideMenuEntry>Bugs and<br>Feature Requests</a></div></div></td><td class=Body width=100%><div class=PageTitle>CSS Styles</div><div class=TOC><a href="#DefaultStyles">Default Styles</a> &middot; <a href="#Customizing">Customizing</a> &middot; <a href="#CommonCustomizations">Common Customizations</a></div><div class=Topic><a name="DefaultStyles"></a><div class=TopicTitle>Default Styles</div><p>These are the styles that come with Natural Docs.&nbsp; They all follow the same color scheme and general layout; the choices are more so that you can choose the style of text you want.</p><p>You choose which style you want for your project by adding &ldquo;<code>-s <i>[style name]</i></code>&rdquo; to the command line.</p><table width=100% border=0 cellspacing=0 cellpadding=0 class=StyleTable><tr><td class=StyleName>Default</td><td class=StyleDescription>This is the default style that Natural Docs uses.&nbsp; Most of the text is 10pt Verdana.</td></tr><tr><td class=StyleName>Small</td><td class=StyleDescription>Smaller fonts than Default with most of the text using 8pt Verdana.&nbsp; Some people like the small fonts because you can fit more on the screen at once.&nbsp; However, some people hate them and find them hard to read.</td></tr><tr><td class=StyleName>Roman</td><td class=StyleDescription>Serif fonts with most of the text using 12pt Roman.&nbsp; Some people prefer Roman fonts, usually those that have decent anti-aliasing displays like Mac OS X or Windows XP with ClearType.</td></tr></table></div><div class=Topic><a name="Customizing"></a><div class=TopicTitle>Customizing</div><p>There are two ways to customize the CSS files.&nbsp; One is to build your own file from scratch, and the other is to make a touch-up file that gets applied after one of the default styles.&nbsp; Either way you want to create your own CSS file in your project directory (the one you use with <code>-p</code>) or if you plan on sharing it between many projects, in Natural Docs&rsquo; Styles directory.</p><p>To use a custom file, no matter where you put it, you just use it with <code>-s</code> without the CSS extension.&nbsp; So if you made Red.css, you use &ldquo;<code>-s Red</code>&rdquo;.&nbsp; If you made a touch-up file instead, you use it after one of the default styles, such as with &ldquo;<code>-s Default Red</code>&rdquo;.&nbsp; If you&rsquo;re so inclined, you can string as many touch-up files together as you want or use one of your own as a base.</p><p>The <a href="http://www.naturaldocs.org/documentation/html/files/Info/CSSGuide-txt.html">CSS Guide</a> documents the page structure and CSS styles of Natural Docs&rsquo; output.&nbsp; Always remember to check its <a href="http://www.naturaldocs.org/documentation/html/files/Info/CSSGuide-txt.html#Revisions">revisions section</a> every time you upgrade Natural Docs because it may change between releases.</p></div><div class=Topic><a name="CommonCustomizations"></a><div class=TopicTitle>Common Customizations</div><a name="WebStyleParagraphs"></a><div class="SubTopic First">Web-Style Paragraphs</div><p>Natural Docs defaults to print-style paragraphs like the one you are reading.&nbsp; Each one is indented and there are no blank lines between them.&nbsp; To switch to web-style paragraphs, which have blank lines and no indents, add this to your custom CSS file:</p><pre class=Example>p {
+   text-indent: 0;
+   margin-bottom: 1em;
+   }
+</pre><a name="PrototypeColors"></a><div class="SubTopic">Prototype Colors</div><p>If you&rsquo;ve <a href="customizingtopics.html#AddingTopicTypes">added a custom topic type</a> and have it <a href="customizinglanguages.html#Prototypes">finding prototypes for you</a>, you may want to have them appear in a different color than the default black and white.&nbsp; Add this to your custom CSS file:</p><pre class=Example>.C<i>[type]</i> .Prototype {
+   background-color: <i>[color]</i>;
+   border-color: <i>[color]</i>;
+   }
+</pre><p>Replace <code><i>[type]</i></code> with the name of your topic type, minus any symbols and spaces.&nbsp; So if you added a type &ldquo;Sound Effect&rdquo;, you would apply the style to &ldquo;<code>.CSoundEffect .Prototype</code>&rdquo;.</p></div></td></tr><tr><td></td><td class=SideMenuBottom><table width=100% border=0 cellspacing=0 cellpadding=0><tr><td class=SideMenuBottomLeft><img src="images/menu/bottomleft.png" width=18 height=19></td><td class=SideMenuBottomRight><img src="images/menu/bottomright.png" width=18 height=19></td></tr></table></td><td class=BodyBottom>Copyright &copy; 2003-2008 Greg Valure</td></tr></table><script language=JavaScript><!--
+ClosingBrowserTags();// --></script></body></html>
\ No newline at end of file

Added: trunk/build/NaturalDocs-1.4/Help/troubleshooting.html
===================================================================
--- trunk/build/NaturalDocs-1.4/Help/troubleshooting.html	                        (rev 0)
+++ trunk/build/NaturalDocs-1.4/Help/troubleshooting.html	2009-07-01 15:30:00 UTC (rev 4275)
@@ -0,0 +1,18 @@
+
+
+<html><head><title>Troubleshooting - Natural Docs</title><link rel=stylesheet type="text/css" href="styles.css"><style type="text/css"><!--
+
+
+            .FAQSummary a:link,
+            .FAQSummary a:visited,
+            .FAQSummary a:hover,
+            .FAQSummary a:active {
+                color: #000000;
+    
+--></style><script language=JavaScript src="javascript/PNGHandling.js"></script><script language=JavaScript src="javascript/BrowserStyles.js"></script></head><body marginwidth=0 marginheight=0 leftmargin=0 topmargin=0><script language=JavaScript><!--
+OpeningBrowserTags();// --></script>
+
+<!-- saved from url=(0026)http://www.naturaldocs.org -->
+
+<table width=100% border=0 cellspacing=0 cellpadding=0><tr><td colspan=3 class=Header><table width=100% border=0 cellspacing=0 cellpadding=0><tr><td><img src="images/header/leftside.png" width=30 height=75><a href="index.html"><img src="images/header/logo.png" width=524 height=75 alt="Natural Docs"></a></td><td align=right><img src="images/header/rightside.png" width=30 height=75></td></tr></table></td></tr><tr><td><img src="images/header/overleftmargin.png" width=10 height=6></td><td class=SideMenuTop><img src="images/header/overmenu.png" width=14 height=6></td><td class=BodyTop><img src="images/header/overbody.png" width=24 height=6></td></tr><tr><td></td><td class=SideMenu nowrap><div class=SideMenuSection><div class=SideMenuTitle><img src="images/menu/about.png" width=52 height=13 alt="About"></div><div class=SideMenuBody><a href="languages.html" class=SideMenuEntry>Language Support</a><a href="output.html" class=SideMenuEntry>Output Formats</a></div></div><div class=SideMenuSection><div class=SideMenuTitle><img src="images/menu/using.png" width=45 height=13 alt="Using"></div><div class=SideMenuBody><a href="documenting.html" class=SideMenuEntry>Documenting<br>Your Code</a><a href="keywords.html" class=SideMenuEntry>Keywords</a><a href="running.html" class=SideMenuEntry>Running</a><span class=SideMenuEntry id=SelectedSideMenuEntry>Troubleshooting</span></div></div><div class=SideMenuSection><div class=SideMenuTitle><img src="images/menu/customizing.png" width=96 height=13 alt="Customizing"></div><div class=SideMenuBody><a href="menu.html" class=SideMenuEntry>Organizing the Menu</a><a href="styles.html" class=SideMenuEntry>CSS Styles</a><a href="customizingtopics.html" class=SideMenuEntry>Topics and Keywords</a><a href="customizinglanguages.html" class=SideMenuEntry>Languages, Indexes,<br>and Prototypes</a></div></div><div class=SideMenuSection><div class=SideMenuTitle><img src="images/menu/community.png" width=86 height=13 alt="Community"></div><div class=SideMenuBody><a href="http://www.naturaldocs.org/" class=SideMenuEntry>Web Site</a><a href="http://www.naturaldocs.org/mailinglist.html" class=SideMenuEntry>Mailing Lists</a><a href="http://www.naturaldocs.org/messageboards.html" class=SideMenuEntry>Message Boards</a><a href="http://www.naturaldocs.org/bugs.html" class=SideMenuEntry>Bugs and<br>Feature Requests</a></div></div></td><td class=Body width=100%><div class=PageTitle>Troubleshooting</div><div class=FAQSummary><div class=FAQSummaryGroup>Natural Docs Issues</div><ul><li><a href="#NoDocs" class=FAQSummaryEntry>I don&rsquo;t get any documentation.</a></li><li><a href="#MissingTopics" class=FAQSummaryEntry>Some of my topics don&rsquo;t show up.</a></li><li><a href="#BadFormatting" class=FAQSummaryEntry>Some of my topics aren&rsquo;t formatting correctly.</a></li><li><a href="#NoPrototypes" class=FAQSummaryEntry>I&rsquo;m not getting prototypes.</a></li><li><a href="#LinksDontResolve" class=FAQSummaryEntry>My links aren&rsquo;t working.</a></li></ul><div class=FAQSummaryGroup>Windows Issues</div><ul><li><a href="#CantFindPerl" class=FAQSummaryEntry>I get the message &ldquo;Bad command or file name&rdquo; or &ldquo;perl is not recognized&rdquo;.</a></li><li><a href="#CantFindND" class=FAQSummaryEntry>I get the message &ldquo;Can&rsquo;t open perl script NaturalDocs&rdquo;.</a></li></ul></div><div class=FAQGroup>Natural Docs Issues</div><div class=FAQEntry><div class=FAQEntryTitle><a name=NoDocs></a>I don&rsquo;t get any documentation</div><div class="First SubTopic">Is it recognizing your source files?</div><p>If Natural Docs has never said &ldquo;Parsing <i>n</i> files...&rdquo; when you run it, or <i>n</i> was way too low a number, it is not finding your source files.</p><p>If it has, try this test.&nbsp; Run Natural Docs once.&nbsp; Edit one of your source files and save it.&nbsp; Run Natural Docs again.&nbsp; If it doesn&rsquo;t say &ldquo;Parsing 1 file...&rdquo; it is not recognizing your file.</p><div class=SubTopic>No, it&rsquo;s not recognizing them</div><p>The most likely scenario is that Natural Docs doesn&rsquo;t associate the file extension you&rsquo;re using with your programming language.&nbsp; Open <code>Languages.txt</code> in Natural Docs&rsquo; Config directory and find your language.&nbsp; Underneath it you should see a line that says something like &ldquo;<code>Extensions: c cpp cxx h hpp hxx</code>&rdquo;.&nbsp; Add the file extensions you use and try again.</p><p>If you use extensionless or .cgi files, do the same thing but instead look for a line that says something like &ldquo;<code>Shebang Strings: tclsh wish expect</code>&rdquo;.&nbsp; If it is not there, you may need to add it yourself.&nbsp; Edit it to include whatever appears in your shebang (<code>#!</code>) line that would say this file belongs to your language.</p><p>Otherwise just make sure you included the directory or one of its parents with <a href="running.html#CommandLine"><code>-i</code> on the command line.</a></p><div class=SubTopic>Yes, it&rsquo;s recognizing them</div><p>First note that unless you have <a href="languages.html">full language support</a>, Natural Docs will only include <a href="documenting.html">what you write for it.</a>&nbsp; It will not be able to scan your code and pick out all the classes and functions on its own.</p><p>If the problem is with text files, the most likely scenario is that you&rsquo;re not <a href="documenting/reference.html#TextFiles">including topic lines.</a>&nbsp; Like in comments, only things that appear under &ldquo;<code>keyword: name</code>&rdquo; lines count as Natural Docs content.</p><p>If this is happening in code, remember that comments must appear alone on a line.&nbsp; You cannot put Natural Docs comments on the same line as code.&nbsp; This includes having anything appear after a closing block comment symbol.</p></div><div class=FAQEntry><div class=FAQEntryTitle><a name=MissingTopics></a>Some of my topics don&rsquo;t show up</div><ul><li><a href="keywords.html">Check the list of keywords</a> to see if the one you&rsquo;re using is there and you spelled it correctly.&nbsp; Note that the web page only has the default set of keywords.&nbsp; You may need to check <code>Topics.txt</code> in Natural Docs&rsquo; Config directory and your project directory if you&rsquo;ve edited them</li><li>If the topics appear in code, make sure that the comments are alone on a line.&nbsp; You cannot put Natural Docs content on the same line as code.&nbsp; This includes having anything appear after a closing block comment symbol.</li><li>Make sure that if you have more than one topic in a comment, there is a blank line above the topic line.</li><li>If you have text boxes or lines, make sure they are completely unbroken.&nbsp; You can also try removing them completely.</li><li>If the topics appear in a text file, make sure you included topic lines.&nbsp; Like in comments, only things that appear after &ldquo;<code>keyword: name</code>&rdquo; lines count as Natural Docs content.&nbsp; You could just add a <code>Title:</code> line to the top of the file to fix this.</li></ul></div><div class=FAQEntry><div class=FAQEntryTitle><a name=BadFormatting></a>Some of my topics aren&rsquo;t formatting correctly</div><ul><li><a href="documenting/reference.html#Headings">Headings</a> must have a blank line above them.</li><li>Lines directly after <a href="documenting/reference.html#BulletLists">bullet</a> or <a href="documenting/reference.html#DefinitionLists">definition</a> lines are part of the previous bullet or definition, even if it&rsquo;s not indented.&nbsp; Skip a line first to do something else</li><li>If you&rsquo;re getting symbols scattered throughout your text, make sure any text boxes or lines are completely unbroken.&nbsp; You can also try removing them altogether.</li><li>If your example source code is getting mangled, remember to use the <a href="documenting/reference.html#CodeAndTextDiagrams">example code syntax</a>.</li><li>If a line&rsquo;s becoming a <a href="documenting/reference.html#Headings">heading</a> but shouldn&rsquo;t, either get rid of the colon at the end or break it into two lines so the colon appears on the second line</li><li>If a line&rsquo;s becoming a <a href="documenting/reference.html#DefinitionLists">definition</a> but shouldn&rsquo;t, either get rid of the space-dash-space (use two dashes or remove one of the spaces) or break it into two lines so that the space-dash-space is on the second line.</li></ul><p>I realize the last two aren&rsquo;t great.&nbsp; If you have any ideas as to how to reliably detect these kinds of false positives, <a href="#" onClick="location.href='mai' + 'lto:' + 'gregv' + 'alure' + '@' + 'natural' + 'docs.org'; return false;">e-mail me</a>.</p></div><div class=FAQEntry><div class=FAQEntryTitle><a name=NoPrototypes></a>I&rsquo;m not getting prototypes</div><ul><li>The topic must appear directly above the thing it&rsquo;s documenting.</li><li>Topics <a href="documenting/reference.html#DefinitionLists">documented in lists</a> will not get prototypes, even if the list break apart in the output.</li><li>The topic name must be present in the prototype somewhere.&nbsp; Make sure the topic title has the same case as in the prototype and that it&rsquo;s not misspelled.&nbsp; This applies even if your language isn&rsquo;t case sensitive.</li><li>If you&rsquo;re documenting something with a new topic type you <a href="customizingtopics.html">added to <code>Topics.txt</code></a>, you must also <a href="customizinglanguages.html#Prototypes">edit <code>Languages.txt</code></a> to tell it how to detect prototypes for that type.</li></ul></div><div class=FAQEntry><div class=FAQEntryTitle><a name=LinksDontResolve></a>My links aren&rsquo;t working</div><p>If your links appear in the output as &ldquo;<code>&lt;text&gt;</code>&rdquo; instead of being converted to links, do the following:</p><ul><li>Make sure the target appears in the output.&nbsp; The easiest way is to see if it appears in the Everything index.</li><li>Make sure the link is spelled correctly and has the same case as what you&rsquo;re linking to.&nbsp; This applies even if your language isn&rsquo;t case sensitive.</li><li>If the topic your link appears in and the link target are not in the same class (or are not both global) make sure you include the class in the link with <code>class.target</code>, <code>class::target</code>, or <code>class-&gt;target</code>.&nbsp; You can check which classes topics appear in with the Everything index.&nbsp; If your topics are appearing in the wrong classes, fix the documentation remembering the <a href="documenting/reference.html#KeywordsTopicsAndScope">topic scoping rules</a>.</li></ul></div><div class=FAQGroup>Windows Issues</div><div class=FAQEntry><div class=FAQEntryTitle><a name=CantFindPerl></a>I get the message &ldquo;Bad command or file name&rdquo; or &ldquo;perl is not recognized&rdquo;</div><p>What&rsquo;s happening is that NaturalDocs.bat can&rsquo;t find Perl.&nbsp; You need Perl installed to run Natural Docs, so if you haven&rsquo;t done so already, you can download and install <a href="http://www.activestate.com/Products/activeperl/">ActiveState&rsquo;s ActivePerl</a> for free.</p><p>If you already have Perl, it&rsquo;s bin directory is either not in your path or the path isn&rsquo;t being used by whatever you&rsquo;re running it from, which happens on some IDEs.&nbsp; Edit NaturalDocs.bat and on the line that says &ldquo;<code>perl NaturalDocs %NaturalDocsParams%</code>&rdquo;, change <code>perl</code> to be the full path to perl.exe, such as <code>&ldquo;C:\perl\bin\perl.exe&rdquo;</code>.&nbsp; You need to include the quotes if there are spaces in the path.</p></div><div class=FAQEntry><div class=FAQEntryTitle><a name=CantFindND></a>I get the message &ldquo;Can&rsquo;t open perl script NaturalDocs&rdquo;</div><p>What&rsquo;s happening is that Perl can&rsquo;t find the Natural Docs script file.&nbsp; This happens when the working directory or &ldquo;start in&rdquo; folder isn&rsquo;t the directory Natural Docs was installed to.&nbsp; If changing that doesn&rsquo;t work, or if you don&rsquo;t have the option to set that, edit NaturalDocs.bat and find the line that says &ldquo;<code>perl NaturalDocs %NaturalDocsParams%</code>&rdquo;.&nbsp; Change <code>NaturalDocs</code> to include the full path Natural Docs was installed to, such as <code>&ldquo;C:\Program Files\Natural Docs\NaturalDocs&rdquo;</code>.&nbsp; You need to include the quotes if there are spaces in the path.</p></div></td></tr><tr><td></td><td class=SideMenuBottom><table width=100% border=0 cellspacing=0 cellpadding=0><tr><td class=SideMenuBottomLeft><img src="images/menu/bottomleft.png" width=18 height=19></td><td class=SideMenuBottomRight><img src="images/menu/bottomright.png" width=18 height=19></td></tr></table></td><td class=BodyBottom>Copyright &copy; 2003-2008 Greg Valure</td></tr></table><script language=JavaScript><!--
+ClosingBrowserTags();// --></script></body></html>
\ No newline at end of file

Added: trunk/build/NaturalDocs-1.4/Info/CSSGuide.txt
===================================================================
--- trunk/build/NaturalDocs-1.4/Info/CSSGuide.txt	                        (rev 0)
+++ trunk/build/NaturalDocs-1.4/Info/CSSGuide.txt	2009-07-01 15:30:00 UTC (rev 4275)
@@ -0,0 +1,947 @@
+
+   Architecture: CSS Structure
+_______________________________________________________________________________
+
+It's important to understand the internal HTML file structure and styles in order to design your own CSS style for Natural Docs.  If
+you're content with the default styles, there's no need to read this document.
+
+Topic: Diagram Conventions
+
+    The diagrams are designed for clarity.  In the actual HTML, you'd obviously see "<table class=CDescriptionList></table>"
+    instead of "<table CDescriptionList></table CDescriptionList>".
+
+    - A tag with just a style, for example "CTitle", means an unspecified element with that class.  Style with .CTitle.
+    - A tag that includes a #, for example "#Menu", means an unspecified element with that ID.  Style with #Menu.
+    - A tag that includes a HTML element as well, for example "table CDescriptionList", means it will always be that element.  You
+      can style with either .CDescriptionList or table.CDescriptionList.
+    - A tag that has multiple classes or has an "and" in it, for example "CType and CTopic", means that both styles will apply to the
+      same element.  You can style it with .CType.CTopic, noting that the space between them must be omitted.
+    - A tag that has an "or" in it, for example "#Content or #Index", is just shorthand for either of those elements.  The diagram
+      applies to both of them but only one will actually appear at a time in the output.
+    - A tag or style with a question mark means that tag or style will only be there in certain situations.
+
+
+Topic: Page Structure
+_______________________________________________________________________________
+
+    The body tag is used to distinguish between the types of pages.
+
+    Unframed Content/Index Page:
+
+        (start diagram)
+
+        <body ContentPage or IndexPage)>
+            [browser styles]
+
+            <#Content or #Index>
+                Content or Index
+            </#Content or #Index>
+
+            <#Menu>
+                Menu
+            </#Menu>
+
+            <#Footer>
+                Footer
+            </#Footer>
+
+            [/browser styles]
+        </body ContentPage or IndexPage)>
+
+        (end diagram)
+
+
+    Unframed Search Results Popup Page:
+
+        (start diagram)
+
+        <body PopupSearchResultsPage>
+            [browser styles]
+
+            <#Index>
+                Index
+            </#Index>
+
+            [browser styles]
+        </body PopupSearchResultsPage>
+
+        (end diagram)
+
+
+    Framed Menu Page:
+
+        (start diagram)
+
+        <body FramedMenuPage>
+            [browser styles]
+
+            <#Menu>
+                Menu
+            </#Menu>
+
+            <#Footer>
+                Footer
+            </#Footer>
+
+            [browser styles]
+        </body FramedMenuPage>
+
+        (end diagram)
+
+
+    Framed Content/Index/SearchResults Page:
+
+        (start diagram)
+
+        <body FramedContentPage or FramedIndexPage or FramedSearchResultsPage>
+            [browser styles]
+
+            <#Content or #Index>
+                Content or Index
+            </#Content or #Index>
+
+            [browser styles]
+        </body FramedContentPage or FramedIndexPage or FramedSearchResultsPage>
+
+        (end diagram)
+
+
+Styles: Page Styles
+
+    ContentPage - An unframed content page.
+    IndexPage - An unframed index page.
+    PopupSearchResultsPage - A search results page for use in a popup iframe.
+
+    FramedContentPage - A framed content page.
+    FramedIndexPage - A framed index page.
+    FramedSearchResultsPage - A framed search results page.
+
+    #Footer - The page footer.  Will be in a framed menu page or on its own in a non-framed page.
+
+    See Also:
+
+        - <#Content>
+        - <#Menu>
+        - <#Index>
+        - <#Footer>
+
+
+
+
+Styles: Browser Styles
+_______________________________________________________________________________
+
+
+    Natural Docs pages include JavaScript to detect which browser the user is running and apply styles so that you can work
+    around browser quirks right in the CSS file.
+
+    The browser type and version styles will be applied immediately after the body tag.  However, neither are guaranteed to be
+    there; the user may have JavaScript turned off or be using a browser that isn't detected.  These styles should only be used to
+    correct minor flaws and should not be heavily relied on.
+
+    >   <body>
+    >       <browser type>?
+    >           <browser version>?
+    >
+    >           Page Content
+    >
+    >           <browser version>?
+    >       <browser type>?
+    >   </body>
+
+    For example, if a <CTopic>'s style is giving you problems in Internet Explorer 6, override it with .IE6 .CTopic.  If a <MTitle>'s
+    style gives you a problem in Opera 7 but only in frames, override it with .Framed.Opera7 .MTitle.
+
+    Browser Types:
+
+        If the browser is not one of the types below, neither this nor the browser version will be present.  There's the possibility that
+        some obscure browser will appear as one of the others by spoofing, but the most prominent of these, Opera, Konqueror, and
+        Safari, are taken care of.
+
+        IE - Internet Explorer
+        Firefox - Firefox and anything else based on the Gecko rendering engine.
+        Opera - Opera
+        Safari - Safari
+        Konqueror - Konqueror and anything else based on the KHTML rendering engine except Safari.
+
+    Browser Versions:
+
+        If the browser is not one of the versions below, this style will not be present.  The browser type still may be.
+
+        IE6 - Internet Explorer 6.x.
+        IE7 - Internet Explorer 7.x.
+
+        Firefox1 - Firefox 1.0.x and anything else based on Gecko 1.7.x.
+        Firefox15 - Firefox 1.5.x and anything else based on Gecko 1.8.0.x.
+        Firefox2 - Firefox 2.0.x and anything else based on Gecko 1.8.1.x.
+
+        Opera7 - Opera 7.x.
+        Opera8 - Opera 8.x.
+        Opera9 - Opera 9.x.
+
+        Safari2 - Safari 2.x.
+        Safari3 - Safari 3.x.
+
+    Notes:
+
+        Why not apply them to the body tag itself?  The JavaScript is easy enough and everything supports multiple classes, right?
+        Because IE 6 doesn't support multiple selectors so I wouldn't be able to combine browser and page styles.
+        .Opera.ContentPage will apply to all ContentPages in IE because it treats it as if only the last class is there.
+
+
+
+
+Topic: Content Structure
+_______________________________________________________________________________
+
+
+    All the topics of a given file is contained in a <#Content>.  All other content styles are prefixed with a C.
+
+    Surrounding each piece of content is a <CTopic> and its type; for example, CFunction for a function.  Inside that are the
+    <CTitle> and if necessary, <CBody>.  Inside <CBody> are analogues to all the top-level <NDMarkup> tags: <h1>, <p>, etc.
+
+    In addition to the top-level <NDMarkup> tags, you also have prototypes, class hierarchies, and summaries which are
+    described in their own sections.
+
+    (start diagram)
+
+    <#Content>
+
+        <CType (CFunction, CVariable, etc.)>
+            <CTopic and #MainTopic?>
+
+                <CTitle>
+                    Topic title
+                </CTitle>
+
+                <CBody>
+
+                    [Class Hierarchy]
+
+                    [Prototype]
+
+                    <CHeading>
+                        Heading
+                    <CHeading>
+
+                    <p>
+                        Paragraph
+                    </p>
+
+                    <pre>
+                        Code or text diagram
+                    </pre>
+
+                    <ul>
+                        <li>
+                            Bullet item
+                        </li>
+                    </ul>
+
+                    <CImageCaption>?
+                        Caption
+                    </CImageCaption>?
+                    <img>
+
+                    <a CImageLink>
+                        text
+                    </a CImageLink>
+
+                    <table CDescriptionList>
+                        <tr>
+                            <td CDLEntry>
+                                Entry
+                            </td CDLEntry>
+                            <td CDLDescription>
+                                Description
+                            </td CDLDescription>
+                        </tr>
+                    </table CDescriptionList>
+
+                    [Summary]
+
+               </CBody>
+
+           </CTopic and #MainTopic?>
+       </CType (CFunction, CVariable, etc.)>
+
+    </#Content>
+
+    (end diagram)
+
+    Take advantange of the CSS inheritance model.  For example, you can style all titles via .CTitle, and you can style
+    specific titles with .CType .CTitle.
+
+
+Styles: Content Styles
+
+    #Content - Parent element containing all topics.
+
+    CTopic - An individual topic.
+
+    CTitle - The title of a topic.
+    CBody - The body of a topic.  May not exist.
+    CHeading - Surrounds a heading.
+    CImageCaption - Surrounds an image caption.
+    CImageLink - Surrounds a link to an image.
+
+    CDescriptionList - A description list, which is the type of list you're reading right now.  Is implemented with a table.
+    CDLEntry - A description list entry, which is the left side.
+    CDLDescription - A description list description, which is the right side.
+
+    #MainTopic - The ID given to the main topic, which is the first in the file.  It is applied to the <CTopic>.
+
+    CType - A placeholder for all type-specific styles.  The actual styles will be C followed by the alphanumeric-only topic type name.
+                So the CType of a "PL/SQL Function" topic will actually be CPLSQLFunction.
+
+
+
+
+Topic: Menu Structure
+_______________________________________________________________________________
+
+
+    Everything is enclosed in a <#Menu>.  All other menu styles are prefixed with an M.
+
+    The title is an <MTitle> and will always be at the beginning of the menu if it exists.  If a subtitle exists as well, it will appear
+    as an <MSubTitle> inside <MTitle>.  Subtitles aren't allowed without titles.  Most other entries in the menu are contained in
+    <MEntries>.  Here's the diagram:
+
+    (start diagram)
+
+    <#Menu>
+
+        <MTitle>
+            Menu title
+
+            <MSubTitle>
+                Menu sub title
+            </MSubTitle>
+
+        </MTitle>
+
+        <MEntry>
+            <MFile (and #MSelected?)>
+                <a href>File</a href>
+            </MFile>
+        </MEntry>
+
+        <MEntry>
+            <MIndex (and #MSelected?)>
+                <a href>File</a href>
+            </MIndex>
+        </MEntry>
+
+        <MEntry>
+            <MText>
+                Text
+            </MText>
+        </MEntry>
+
+        <MEntry>
+            <MLink>
+                <a href>Link</a href>
+            </MLink>
+        </MEntry>
+
+        <MEntry>
+            <MGroup>
+                <a href>Group</a href>
+                <MGroupContent>
+
+                    (MEntries)
+
+                </MGroupContent>
+           </MGroup>
+        </MEntry>
+
+        <#MSearchPanel and MSearchPanelActive/Inactive>
+            <input #MSeachField>
+            <select #MSearchType>
+                <option #MSearchEverything>
+                <option>
+                <option>
+            </select #MSearchType>
+        </#MSearchPanel and MSearchPanelActive/Inactive>
+
+    </#Menu>
+
+    (if in unframed HTML)
+    <#MSearchResultsWindow>
+
+        <iframe #MSearchResults>
+        </iframe #MSearchResults>
+
+        <a #MSearchResultsWindowClose>
+
+    </#MSearchResultsWindow>
+
+    (end)
+
+    The <MFile> or <MIndex> entry that's currently selected will have the <#MSelected> ID, so you can reference it in CSS via
+    .MFile#MSelected.
+
+    The search panel is has its own ID, <#MSearchPanel>, but also has one of the classes <MSearchPanelActive> or
+    <MSearchPanelInactive> depending on whether any of the controls are selected or the results window is open.
+    <#MSearchResultsWindow> is separate because it may be floating.
+
+
+Styles: Menu Styles
+
+    #Menu - Parent element containing the entire menu.
+
+    MTitle - The title of the menu.
+    MSubTitle - The subtitle of the menu.  Will appear within <MTitle>.
+
+    MFile - A file entry.
+    MGroup - A group entry.
+    MGroupContent - A container for a <MGroup's> content.
+    MText - A plain text entry.
+    MLink - An external link entry.
+    MIndex - An index entry.
+
+    #MSelected - The ID of the currently selected <MFile> or <MIndex>.
+
+    MType - <MFile>, <MGroup>, <MText>, <MLink>, or <MIndex>.
+
+    #MSearchPanel - Contains all the search controls.
+    MSearchPanelActive - Applied to <#MSearchPanel> when any of the controls are selected or the results window is open.
+    MSearchPanelInactive - Applied to <#MSearchPanel> when not in use.
+
+    #MSearchField - The text input field of the search panel.
+    #MSearchType - The drop down type selector of the search panel.
+    #MSearchEverything - The <#MSearchType> option for the Everything index.
+
+    #MSearchResultsWindow - Contains all the search results elements.
+    #MSearchResults - Contains the iframe that will hold the results.
+    #MSearchRseultsWindowClose - The link to manually close the search results window.
+
+
+
+
+Topic: Class Hierarchy Structure
+_______________________________________________________________________________
+
+
+    Everything is contained in a single <ClassHierarchy>.  Each entry is surrounded by its type, such as <CHParent>, and the
+    generic <CHEntry>.  Depending on the context, entries may be surrounded by one or more <CHIndents>.
+
+    (start diagram)
+
+    <ClassHierarchy>
+
+        <CHIndent>?
+
+            <CHType>
+                <CHEntry>
+
+                    <a href>?
+                        Entry
+                    </a href>
+
+                </CHEntry>
+            </CHType>
+
+        </CHIndent>?
+
+    </ClassHierarchy>
+
+    (end diagram)
+
+
+Styles: Class Hierarchy Styles
+
+    ClassHierarchy - The topmost style containing everything.
+
+    CHEntry - A generic class entry.
+
+    CHParent - The style for a parent class.
+    CHCurrent - The style for the current class, which is the one the hierarchy is generated for.
+    CHChild - The style for a child class.
+    CHChildNote - The style for when a child is added that just shows how many other children were omitted.
+
+    CHIndent - A style used to indent a level.
+
+    CHType - <CHParent>, <CHCurrent>, <CHChild>, or <CHChildNote>.
+
+
+
+
+Topic: Summary Structure
+_______________________________________________________________________________
+
+
+    Everything is enclosed in a single <Summary>.  All the other summary styles are prefixed with an S.
+
+    <STitle> holds the actual word "Summary" and <SBorder> and <STable> hold the content.  <SBorder> exists because different
+    browsers apply table padding attributes in different ways.  <STable> exists as a class to separate the main table from any other
+    tables that may be necessary.  Here's a diagram:
+
+    >   <Summary>
+    >
+    >       <STitle>
+    >           Title
+    >       </STitle>
+    >
+    >       <SBorder>
+    >           <table STable>
+    >               ...
+    >           </table STable>
+    >       </SBorder>
+    >
+    >   </Summary>
+
+    On to the table content.
+
+    >   <tr SType and SEntry (and SIndent#?) (and SMarked?)>
+    >       <td SEntry>
+    >
+    >           <a href>Entry</a href>
+    >
+    >       </td SEntry>
+    >       <td SDescription>
+    >
+    >           Description
+    >
+    >       </td SDescription>
+    >   </tr SType and SEntry (and SIndent#?) (and SMarked?)>
+
+    <SIndent#> exist to allow indenting.  They're necessary because implementing it as nested tables, while structurally cleaner,
+    won't allow the desciptions to line up on the right throughout the entire summary.  <SMarked> will be applied on almost every
+    other row to allow for tinting to improve readability.
+
+    Use the power of CSS's inheritance rules to specify styles.  For example, to set the style of a group entry, apply it to
+    .SGroup .SEntry.  However, you could also apply a style to both the group's entry and description by applying the
+    style to .SGroup td.  Or, you could apply a style to all the entries by applying it to .SEntry.  And so on.
+
+
+Styles: Summary Styles
+
+    Summary - The topmost style containing the entire summary.
+
+    STitle - Contains the summary title, which is the part that actually says "Summary".
+
+    SBorder - Surrounds <STable>, since some browsers can't do table padding right.  A hack, I know.
+    STable - The actual summary table.  This class separates it from other layout tables that may appear.
+
+    SMarked - A class applied to rows that should have a slightly different color than the rest of the rows to make them easier to
+                    read.
+
+    SEntry - The entry (left) side of the table.
+    SDescription - The description (right) side of the table.
+
+    SIndent# - Surrounding entries and descriptions that are part of a group and need to be indented.  Actual styles will be
+                     SIndent1, SIndent2, etc.
+
+    SType - A placeholder for all topic-specific styles.  The actual styles will be S followed by the alphanumeric-only topic type name.
+                So the SType of a "PL/SQL Function" topic will actually be SPLSQLFunction.
+
+
+
+
+Topic: Prototype Structure
+_______________________________________________________________________________
+
+
+    Everything is enclosed in a <Prototype>.  All other styles are prefixed with a P.
+
+    Parameter Type First Style:
+
+        For prototypes such as
+        > void Function (unsigned int* a, int b = 0)
+        where the types come first.
+
+        (start diagram)
+
+        <table Prototype>
+
+            <td PBeforeParameters>
+                "void Function ("
+            </td PBeforeParameters>
+
+            <td PTypePrefix>
+                "unsigned"
+            </td PTypePrefix>
+
+            <td PType>
+                "int"
+            </td PType>
+
+            <td PParameterPrefix>
+                "*"
+            </td PParameterPrefix>
+
+            <td PParameter>
+                "a", "b"
+            </td PParameter>
+
+            <td PDefaultValuePrefix>
+                "="
+            </td PDefaultValuePrefix>
+
+            <td PDefaultValue>
+                "0"
+            </td PDefaultValue>
+
+            (repeated as necessary)
+
+            <td PAfterParameters>
+                ")"
+            </td PAfterParameters>
+
+        </table Prototype>
+
+        (end diagram)
+
+
+    Parameter Name First Style:
+
+        For prototypes such as
+        > function Function (a, b: int; c: int := 0)
+        where the parameters come first.
+
+        (start diagram)
+
+        <table Prototype>
+
+            <td PBeforeParameters>
+                "function Function ("
+            </td PBeforeParameters>
+
+            <td PParameter>
+                "a,", "b:", "c:"
+            </td PParameter>
+
+            <td PType>
+                "int"
+            </td PType>
+
+            <td PDefaultValuePrefix>
+                ":="
+            </td PDefaultValuePrefix>
+
+            <td PDefaultValue>
+                "0"
+            </td PDefaultValue>
+
+            (repeated as necessary)
+
+            <td PAfterParameters>
+                ")"
+            </td PAfterParameters>
+
+        </table Prototype>
+
+        (end diagram)
+
+
+    Note that any section may not exist.  For example, there will be no <PTypePrefix> cells generated if none of the parameters
+    have it.
+
+
+Styles: Prototype Styles
+
+    Prototype - The style encompassing the entire prototype.
+
+    PBeforeParameters - The part of the prototype that comes before the parameters.
+    PAfterParameters - The part of the prototype that comes after the parameters.
+
+    PType - The parameter type.
+    PTypePrefix - The prefix of a parameter type.
+    PParameter - The parameter name.
+    PParameterPrefix - The prefix of a parameter name.
+    PDefaultValue - The default value expression for a parameter.
+    PDefaultValuePrefix - The prefix of the default value expression.
+
+
+
+
+Topic: Link Structure
+_______________________________________________________________________________
+
+
+    All links to symbols have a type style prefixed with L.  The only exceptions are summary entries; summary descriptions use
+    them as well.
+
+    >   <a LType>
+    >       Link
+    >   </a LType>
+
+    You can use this to make links to different symbols appear in different styles.  For example, making .LClass bold will make all
+    links to classes bold, except when appearing in summary entries.  You can combine this with other styles to be even more
+    specific.  For example, you can apply a style to function links appearing in summary descriptions with .SDescription .LFunction.
+
+Styles: Link Styles
+
+    LType - A placeholder for all topic-specific styles.  The actual styles will be L followed by the alphanumeric-only topic type name.
+                So the LType of a "PL/SQL Function" topic will actually be LPLSQLFunction.
+
+
+
+Topic: Index Structure
+_______________________________________________________________________________
+
+
+    Everything is enclosed in an <#Index>.  Combine with <Framed> and <Unframed> to distinguish between output formats.  All
+    other index styles are prefixed with an I.
+
+    (start diagram)
+
+    <#Index>
+
+        <IPageTitle>
+            Page Title
+        </IPageTitle>
+
+        <INavigationBar>
+            A - <a href>B</a href> - C ...
+        </INavigationBar>
+
+        <table>
+
+            <IHeading>
+                Heading (A, B, etc.)
+            </IHeading>
+
+            <td ISymbolPrefix>
+                Prefix, if any
+            </td ISymbolPrefix>
+
+            <td IEntry>
+                Entry
+            </td IEntry>
+
+            ...
+
+        </table>
+
+    </#Index>
+
+    (end diagram)
+
+    Every index entry, including headings, are rows in a table.  The first column of a non-heading are <ISymbolPrefixes> so that
+    the non-prefix portions align correctly.  The other column are <IEntries>, of which there are multiple formats, described below.
+
+    (start diagram)
+
+    <a href ISymbol>
+        Symbol
+    </a href ISymbol>,
+    <IParent>
+        Class
+    </IParent>
+
+    <ISymbol>
+        Symbol
+    </ISymbol>
+    <ISubIndex>
+        <a href IParent>
+            Class
+        </a href IParent>
+        ...
+    </ISubIndex>
+
+    <ISymbol>
+        Symbol
+    </ISymbol>
+    <ISubIndex>
+        <IParent>
+            Class
+        </IParent>
+        <ISubIndex>
+            <a href IFile>
+                File
+            </a href IFile>
+            ...
+        </ISubIndex>
+        ...
+    </ISubIndex>
+
+    (end diagram)
+
+    Each part of the entry is surrounded by its type, which may or may not be a link.  If an entry has more than one defining class
+    or file,  they're broken out into <ISubIndexes>.
+
+    It's called <IParent> instead of <IClass> because class entries are <ISymbols>.  <IParents> are only used when the symbol
+    has a class.  If the symbol _is_ a class, the symbol is global.
+
+
+Styles: Index Styles
+
+    #Index - Parent element for the entire index.
+
+    IPageTitle - The page title.
+    INavigationBar - The navigation bar.
+
+    IHeading - An index heading, such as the letter for the group.
+
+    IEntry - An entry in the index.
+    ISymbolPrefix - The stripped prefix of the entry.
+    ISymbol - The entry symbol.
+    IParent - The entry parent class.  If the entry _is_ a class, this isn't defined because classes are global and don't have parent
+                  classes.  This is why it's called IParent instead of IClass; hopefully it's less confusing.
+    IFile - The file the entry is defined in.
+
+    ISubIndex - The surrounding block if an entry needs to be broken out into a sub-index.
+
+    #IFirstHeading - The ID of the first <IHeading> to appear in the file.
+
+    #IFirstSymbolPrefix - The ID for the first <ISymbolPrefix> to appear under an <IHeading>.
+    #ILastSymbolPrefix - The ID for the last <ISymbolPrefix> to appear under an <IHeading>.
+    #IOnlySymbolPrefix - The ID if there is only one <ISymbolPrefix> for an <IHeading>.
+
+
+
+Topic: Search Results Structure
+_______________________________________________________________________________
+
+
+    The search results use virtually the same structure and styles as the indexes, except that <#SearchResults> replaces
+    <#Index>, there's a new <SRResult> style, and there are a few additional <SRStatus> blocks.
+
+    Visibility:
+
+        Visibility is *very* important to making the search work correctly.  JavaScript will handle most of it, but your CSS needs to
+        abide by these rules.
+
+        - <SRStatus> sections are visible by default.
+        - <SRResult> sections are *not* visible by default.  They must use display: none.
+        - <ISubIndex> should be display: none when under <#SearchResults>.
+
+
+Styles: Search Results Styles
+
+    #SearchResults - Parent element for the entire page.
+    SRStatus - Status message.  Must be visible by default.
+    SRResult - A result.  All you need to do for this class is set it to display: none.  Nothing else should be set on it.
+
+
+
+
+Topic: Tool Tip Structure
+_______________________________________________________________________________
+
+
+    Tool tips may appear anywhere in the page, mainly because it's assumed that they will use position: absolute and
+    visibility: hidden.
+
+    The entire tool tip is found in a <CToolTip> style, with a CType style inside it.  CTypes are normally outside their elements, but
+    that would cause it to be partially visible in this case.  We need <CToolTip> to be the outermost style so its visibility and
+    position can be manipulated in JavaScript.
+
+    Inside there's a <CPrototype> and/or the description text.  The description text has no special surrounding tags.
+
+    >   <CToolTip>
+    >
+    >       <CPrototype>
+    >           Prototype
+    >       </CPrototype>
+    >
+    >       Summary text
+    >
+    >   </CToolTip>
+
+Styles: Tool Tip Styles
+
+    CToolTip - Surrounds the entire tool tip.  This *must* have position: absolute and visibility: hidden for the tool tip mechanism
+                    to work.
+
+    See also <CPrototype>.
+
+
+Styles: Miscellaneous Styles
+
+    blockquote - This HTML element should surround anything that needs to be scrolled if it's too wide, like prototypes and text
+                       diagrams.  It's not a style because this makes it much easier to do the JavaScript necessary to get this working
+                       in IE.
+
+
+Group: History
+
+Topic: Revisions
+_______________________________________________________________________________
+
+
+    How the page structure has changed throughout the various releases.
+
+    1.4:
+
+        - Replaced UnframedPage with <ContentPage> and <IndexPage>.
+        - Added <#Menu>, <#Content>, <#Footer>, and <#Index>.  They were previously shown in the diagrams as classes but did
+          not actually appear in the generated output.
+        - Removed MenuSection, ContentSection, and IndexSection.  Use things like ".ContentPage #Menu" instead.
+        - Removed tables from the unframed <Page Structure>.  Use CSS to position the elements instead.
+        - <#MainTopic> is applied to <CTopic> instead of <CType>.
+        - IE4, IE5, Opera5, Opera6, Netscape, and Netscape4 browser styles have been removed.  <IE7>, <Opera8>,
+          and <Opera9> have been added.  Gecko has been replaced by <Firefox>, <Firefox1>, <Firefox15>, and <Firefox2>.
+          KHTML has been replaced by <Safari>, <Safari2>, <Safari3>, and <Konqueror>.
+        - Removed redundant CParagraph, CCode, and CBulletList classes.  Use <CBody> with p, pre, and ul instead.
+        - Added <CImageCaption> and <CImageLink>.
+        - Added <#MSearchPanel>, <#MSearchResultsWindow>, and all related styles.
+        - Added <Search Results Structure>, <Search Results Styles>, and <FramedSearchResultsPage>.
+        - Removed SEntrySize.  Apply the width to <SEntry> and <SDescription> instead.
+        - <SType>, <SEntry>, and <SIndent#> were moved from the td and divs into the tr.
+        - Removed HB style.  Now using wbr tag.
+
+    1.33:
+
+        - Added <PDefaultValuePrefix>.
+
+    1.32:
+
+        - <blockquotes> now surround elements that should scroll if they're too wide for the page.
+
+    1.3:
+
+        - Removed CPrototype.  See the replacement <Prototype Structure> and <Prototype Styles>.
+        - Removed SInGroup, SInClass, and SInSection in favor of more general <SIndent#>.
+        - <CTypes>, <STypes>, and <LTypes> are now completely determined by <Topics.txt> configuration files.
+        - <CTypes>, <STypes>, and <LTypes> no longer have separate list types.  A CFunctionList is now just a CFunction.
+        - Indexes are now done with tables.
+        - ISection was removed.
+        - <IEntries> are only used for the entry cell, not for each entry in an <ISubIndex>.
+        - Added <ISymbolPrefix>, related IDs, and <#IFirstHeading>.
+        - Merged <CType> and <CTopic> into the same element.  Must now be styled with .CType.CTopic (no space) while all
+          sub-elements will still be .CType .CElement (with space.)
+
+    1.21:
+
+        - Added <TOPIC_PROPERTY> and TOPIC_PROPERTY_LIST styles, so they get corresponding <CTypes>, <STypes>, and
+          <LTypes>.
+
+    1.2:
+
+        - Added <Class Hierarchy Styles> since 1.2 added class hierarchies.
+
+    1.16:
+
+        - Changed the first topic from having a CMain type to having a normal type with a <#MainTopic> ID.
+
+    1.1:
+
+        - Added <Tool Tip Styles>.
+        - Renamed HiddenBreak to <HB>.
+        - Added <TOPIC_CONSTANT>, TOPIC_CONSTANT_LIST, <TOPIC_TYPE>, and TOPIC_TYPE_LIST types, so they get
+          corresponding <CTypes>, <STypes>, and <LTypes>.
+
+    1.0:
+
+        - The <CType> tags now appear arround the <CTopic> tags instead of vice versa.
+        - Added a <CBody> tag to surround non-<CTitle> elements.
+        - <SMarked> now appears in tr's instead of td's, where it belonged in the first place.
+
+    0.95:
+
+        - Added <Browser Styles>.
+        - Redid <Page Structure>, replacing generic styles like Menu with page type styles like UnframedPage/MenuSection and
+          FramedMenuPage.
+
+    0.91:
+
+        - Added <LURL> and <LEMail> link styles, since 0.91 added URL and e-mail links.
+        - Added <ISection> style, which is better than <IHeading> floating on its own.
+
+    0.9:
+
+        - Added <Index Styles>, since 0.9 added indexes.
+

Added: trunk/build/NaturalDocs-1.4/Info/File Parsing.txt
===================================================================
--- trunk/build/NaturalDocs-1.4/Info/File Parsing.txt	                        (rev 0)
+++ trunk/build/NaturalDocs-1.4/Info/File Parsing.txt	2009-07-01 15:30:00 UTC (rev 4275)
@@ -0,0 +1,83 @@
+
+    Architecture: File Parsing
+
+####################################################################################
+
+    This is the architecture and code path for general file parsing.  We pick it up at <NaturalDocs::Parser->Parse()> because we're not interested in how the files are gathered and their languages determined for the purposes of this document.  We are just interested in the process each individual file goes through when it's decided that it should be parsed.
+
+
+
+    Stage: Preparation and Differentiation
+    _______________________________________________________________________________________________________
+
+    <NaturalDocs::Parser->Parse()> can be called from one of two places, <NaturalDocs::Parser->ParseForInformation()> and <NaturalDocs::Parser->ParseForBuild()>, which correspond to the parsing and building phases of Natural Docs.  There is no noteworthy work done in either of them before they call Parse().
+
+
+    Stage: Basic File Processing
+    _______________________________________________________________________________________________________
+
+    The nitty-gritty file handling is no longer done in <NaturalDocs::Parser> itself due to the introduction of full language support in 1.3, as it required two completely different code paths for full and basic language support.  Instead it's handled in NaturalDocs::Languages::Base->ParseFile(), which is really a virtual function that leads to <NaturalDocs::Languages::Simple->ParseFile()> for basic language support or a version appearing in a package derived from <NaturalDocs::Languages::Advanced> for full language support.
+
+    The mechinations of how these functions work is for another document, but their responsibility is to feed all comments Natural Docs should be interested in back to the parser via <NaturalDocs::Parser->OnComment()>.
+
+
+    Stage: Comment Processing
+    _______________________________________________________________________________________________________
+
+    <NaturalDocs::Parser->OnComment()> receives the comment sans comment symbols, since that's language specific.  All comment symbols are replaced by spaces in the text instead of removed so any indentation is properly preserved.  Also passed is whether it's a JavaDoc styled comment, as that varies by language as well.
+
+    OnComment() runs what it receives through <NaturalDocs::Parser->CleanComment()> which normalizes the text by removing comment boxes and horizontal lines, expanding tabs, etc.
+
+
+    Stage: Comment Type Determination
+    _______________________________________________________________________________________________________
+
+    OnComment() sends the comment to <NaturalDocs::Parser::Native->IsMine()> to test if it's definitely Natural Docs content, such as by starting with a recognized header line.  If so, it sends it to <NaturalDocs::Parser::Native->ParseComment()>.
+
+    If not, OnComment() sends the comment to <NaturalDocs::Parser::JavaDoc->IsMine()> to test if it's definitely JavaDoc content, such as by having JavaDoc tags.  If so, it sends it to <NaturalDocs::Parser::JavaDoc->ParseComment()>.
+
+    If not, the content is ambiguous.  If it's a JavaDoc-styled comment it goes to <NaturalDocs::Parser::Native->ParseComment()>  to be treated as a headerless Natural Docs comment.  It is ignored otherwise, which lets normal comments slip through.  Note that it's only ambiguous if neither parser claims it; there's no test to see if they both do.  Instead Natural Docs always wins.
+
+    We will not go into the JavaDoc code path for the purposes of this document.  It simply converts the JavaDoc comment into <NDMarkup> as best it can, which will never be perfectly, and adds a <NaturalDocs::Parser::ParsedTopic> to the list for that file.  Each of those ParsedTopics will be headerless as indicated by having an undefined <NaturalDocs::Parser::ParsedTopic->Title()>.
+
+
+    Stage: Native Comment Parsing
+    _______________________________________________________________________________________________________
+
+    At this point, parsing is handed off to <NaturalDocs::Parser::Native->ParseComment()>.  It searches for header lines within the comment and divides the content into individual topics.  It also detects (start code) and (end) sections so that anything that would normally be interpreted as a header line can appear there without breaking the topic.
+
+    The content between the header lines is sent to <NaturalDocs::Parser::Native->FormatBody()> which handles all the block level formatting such as paragraphs, bullet lists, and code sections.  That function in turn calls <NaturalDocs::Parser::Native->RichFormatTextBlock()> on certain snippets of the text to handle all inline formatting, such as bold, underline, and links, both explicit and automatic.
+
+    <NaturalDocs::Parser::Native->ParseComment()> then has the body in <NDMarkup> so it makes a <NaturalDocs::Parser::ParsedTopic> to add to the list.  It keeps track of the scoping via topic scoping, regardless of whether we're using full or basic language support.  Headerless topics are given normal scope regardless of whether they might be classes or other scoped types.
+
+
+    Group: Post Processing
+    _______________________________________________________________________________________________________
+
+    After all the comments have been parsed into ParsedTopics and execution has been returned to <NaturalDocs::Parser->Parse()>, it's time for some after the fact cleanup.  Some things are done like breaking topic lists, determining the menu title, and adding automatic group headings that we won't get into here.  There are two processes that are very relevant though.
+
+
+    Stage: Repairing Packages
+    _______________________________________________________________________________________________________
+
+    If the file we parsed had full language support, the <NaturalDocs::Languages::Advanced> parser would have done more than just generate various OnComment() calls.  It would also return a scope record, as represented by <NaturalDocs::Languages::Advanced::ScopeChange> objects, and a second set of ParsedTopics it extracted purely from the code, which we'll refer to as autotopics.  The scope record shows, purely from the source, what scope each line of code appears in.  This is then combined with the topic scoping to update ParsedTopics that come from the comments in the function <NaturalDocs::Parser->RepairPackages()>.
+
+    If a comment topic changes the scope, that's honored until the next autotopic or scope change from the code.  This allows someone to document a class that doesn't appear in the code purely with topic scoping without throwing off anything else.  Any other comment topics have their scope changed to the current scope no matter how it's arrived at.  This allows someone to manually document a function without manually documenting the class and still have it appear under that class.  The scope record will change the scope to part of that class even if topic scoping did not.  Essentially the previous topic scoping is thrown out, which I guess is something that can be improved.
+
+    None of this affects the autotopics, as they are known to have the correct scoping since they are gleaned from the code with a dedicated parser.  Wouldn't there be duplication of manually documented code elements, which would appear both in the autotopics and in the comment topics?  Yes.  That brings us to our next stage, which is...
+
+
+    Stage: Merging Auto Topics
+    _______________________________________________________________________________________________________
+
+    As mentioned above, ParseFile() also returns a set of ParsedTopics gleaned from the code called autotopics.  The function <NaturalDocs::Parser->MergeAutoTopics()> merges this list with the comment topics.
+
+    The list is basically merged by line number.  Since named topics should appear directly above the thing that they're documenting, topics are tested that way and combined into one if they match.  The description and title of the comment topic is merged with the prototype of the autotopic.  JavaDoc styled comments are also merged in this function, as they should appear directly above the code element they're documenting.  Any headerless topics that don't, either by appearing past the last autotopic or above another comment topic, are discarded.
+
+
+    Stage: Conclusion
+    _______________________________________________________________________________________________________
+
+    Thus ends all processing by <NaturalDocs::Parser->Parse()>.  The file is now a single list of <NaturalDocs::Parser::ParsedTopics> with all the body content in <NDMarkup>.  If we were using <NaturalDocs::Parser->ParseForBuild()>, that's pretty much it and it's ready to be converted into the output format.  If we were using <NaturalDocs::Parser->ParseForInformation()> though, the resulting file is scanned for all relevant information to feed into other packages such as <NaturalDocs::SymbolTable>.
+
+    Note that no prototype processing was done in this process, only the possible tranferring of prototypes from one ParsedTopic to another when merging autotopics with comment topics.  Obtaining prototypes and formatting them is handled by <NaturalDocs::Languages::Simple> and <NaturalDocs::Languages::Advanced> derived packages.

Added: trunk/build/NaturalDocs-1.4/Info/HTMLTestCases.pm
===================================================================
--- trunk/build/NaturalDocs-1.4/Info/HTMLTestCases.pm	                        (rev 0)
+++ trunk/build/NaturalDocs-1.4/Info/HTMLTestCases.pm	2009-07-01 15:30:00 UTC (rev 4275)
@@ -0,0 +1,269 @@
+###############################################################################
+#
+#   File: Browser Testing
+#
+###############################################################################
+#
+#   This file tests Natural Docs' generated output.  Particularly useful when testing various browsers.
+#
+###############################################################################
+
+# 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;
+
+
+#
+#   About: Browsers
+#
+#   The specific browser versions tested are below.  Everything is tested on Windows Vista unless otherwise noted.
+#
+#   Firefox 2.0.0.10 - 2.0 released October 2006.
+#   Firefox 1.5.0.8 - 1.5 released Novemer 2005.
+#   Firefox 1.0.8 - 1.0 released November 2004.  Not critical to support.
+#
+#   IE 7.0 - 7.0 released October 2006.
+#   IE 6.0 - 6.0 released August 2001.  Tested on Windows XP SP2 via Virtual PC.
+#
+#	Safari 3.0.4 - 3.0 released June 2007.  Tested Windows version.
+#	Safari 2.0.4 - 2.0 released April 2005.  Tested on Mac OS X 10.4 Tiger.
+#
+#   Opera 9.02 - 9.0 released June 2006.
+#   Opera 8.54 - 8.5 released September 2005.
+#   Opera 8.02 - 8.0 released April 2005.
+#   Opera 7.51 - 7.5 released around August 2004 I think.  Not critical to support.
+#   Opera 7.02 - 7.0 released January 2003.  Not critical to support.
+#
+#   Konqueror 3.5.5 - Tested on openSUSE 10.2 via VMware Player.
+#
+
+
+###############################################################################
+# Group: Search
+
+#
+#   Topic: Unframed HTML Search
+#
+#   Tests:
+#
+#       - Make sure the search box appears and disappears correctly on hover.
+#       - Type to bring up results.  Type further to narrow them.  Narrow until there's no results.
+#       - Backspace to bring the results back.  Backspacing to empty closes the results.
+#       - Type to bring up results with a different first letter.  (Tests iframe content switch.)
+#       - Type *Z* to bring up empty page when there's nothing with that first letter.  (Tests generic no results page.)
+#       - Type *Name* in Everything search to test expanding and collapsing, especially between two that differ only by case.
+#       - Change filter to *Functions* to test changing filter while results are open.  Change to *Types* to switch to one with
+#         no results.
+#       - Test Close button on results.  Should deactivate panel as well.
+#       - Clicking away should deactivate panel if the box is empty, not have an effect if there are results open.
+#       - Text should always change back to "Search" when deactivating.
+#
+#   Results:
+#
+#       Firefox 2.0  - OK
+#       Firefox 1.5  - OK
+#       Firefox 1.0  - OK
+#
+#       IE 7.0  - OK
+#       IE 6.0  - Functionally OK.  Search panel doesn't activate on hover.  Works fine when clicked.
+#
+#		Safari 3.0  - OK
+#		Safari 2.0  - *Broken.*  Results panel doesn't show up.  Border around deactivated search box.
+#
+#       Opera 9.0  - OK
+#       Opera 8.5  - OK
+#       Opera 8.0  - OK
+#       Opera 7.5  - Functionally OK.  Search panel has sunken border when deactivated, minor pixel shifting.
+#       Opera 7.0  - *Broken.*  Completely.
+#
+#       Konqueror 3.5  - *Broken.*  Results panel doesn't show up.  Seems to fail on "resultsFrame = window.frames.MSearchResults;"
+#
+
+#
+#   Topic: Framed HTML Search
+#
+#   Tests:
+#
+#       - Make sure the search box appears and disappears correctly on hover.
+#       - Type to bring up results on right.  Type further to narrow them.  Narrow until there's no results.
+#       - Backspace to bring the results back.
+#       - Type to bring up results with a different first letter.  (Tests frame content switch.)
+#       - Type *Z* to bring up empty page when there's nothing with that first letter.  (Tests generic no results page.)
+#       - Type *Name* in Everything search to see that there's no collapsing in this mode.
+#       - Change filter to *Functions* to test changing filter while results are open.  Change to *Types* to switch to one with
+#         no results.
+#       - Clicking away should deactivate panel.
+#       - Clicking a result should deactivate panel and show up in correct frame.
+#       - Text should always change back to "Search" when deactivating.
+#
+#   Results:
+#
+#       Firefox 2.0  - OK
+#       Firefox 1.5  - OK
+#       Firefox 1.0  - OK
+#
+#       IE 7.0  - OK
+#       IE 6.0  - Functionally OK.  Search panel doesn't activate on hover, is a little wide.  Works fine when clicked.
+#
+#		Safari 3.0  - OK
+#		Safari 2.0  - Functionally OK.  Has a sunken border around the deactivated seach field.
+#
+#       Opera 9.0  - OK
+#       Opera 8.5  - OK
+#       Opera 8.0  - OK
+#       Opera 7.5  - Functionally OK.  Search panel has sunken border when deactivated, minor pixel shifting.
+#       Opera 7.0  - *Broken.*
+#
+#       Konqueror 3.5  - Functionally OK.  Panel doesn't reset and deactivate when clicking a result link.
+#
+
+
+###############################################################################
+# Group: Other
+
+#
+#   Topic: Images
+#
+#	Tests:
+#
+#   - Here is an embedded image on its own line.
+#
+#   (see images/logo.png)
+#
+#   - Here is a reference in the middle of a sentence, in the middle of a bullet list: (see images/logo.png)  It should have been
+#     converted to a link with the image appearing below the bullet list and the file name used as a caption.  Make sure the
+#     caption positions correctly.
+#   - Here's a link to a non-existent image, which should appear literally: (see images/doesntexist.jpg)
+#   - Here is an embedded image that doesn't exist on it's own line.
+#
+#   (see images/doesntexist.png)
+#
+#   - Here is a link using the "(see)" syntax which shouldn't be interpreted as an image link because it doesn't end with an
+#     acceptable extension.  Also, links should still resolve because of that.  (see <Framed HTML Search>)
+#
+#	Results:
+#
+#       Firefox 2.0  - OK
+#       Firefox 1.5  - OK
+#       Firefox 1.0  - OK
+#
+#       IE 7.0  - OK
+#       IE 6.0  - OK
+#
+#		Safari 3.0  - OK
+#		Safari 2.0  - OK
+#
+#       Opera 9.0  - OK
+#       Opera 8.5  - OK
+#       Opera 8.0  - OK
+#       Opera 7.5  - OK
+#       Opera 7.0  - OK
+#
+#       Konqueror 3.5  - OK
+
+
+#
+#	Topic: Prototypes and Tooltips
+#
+#	Hover over <NaturalDocs::Parser::JavaDoc->ParseComment()> and <NaturalDocs::Parser::JavaDoc->IsMine()>
+#
+#	Tests:
+#
+#		- A tooltip should appear about a second after you hover over the link above.
+#		- It should go away when you move the cursor away.
+#		- It shoud be positioned directly underneath with a slight gap.
+#		- The prototype should be formatted cleanly with each parameter on its own line and aligned in columns.
+#		- The asterisk should be in a separate column.
+#		- Test it with the link too close to the edge of the window so the pop-up has to shift left to fit.
+#
+#	Results:
+#
+#       Firefox 2.0  - OK
+#       Firefox 1.5  - OK
+#       Firefox 1.0  - OK
+#
+#       IE 7.0  - OK
+#       IE 6.0  - OK
+#
+#		Safari 3.0  - OK
+#		Safari 2.0  - OK
+#
+#       Opera 9.0  - OK.  Has its own tooltips turned on by default which can cover it up though.
+#       Opera 8.5  - OK.  Has its own tooltips turned on by default which can cover it up though.
+#       Opera 8.0  - OK.  Has its own tooltips turned on by default which can cover it up though.
+#       Opera 7.5  - OK.  Has its own tooltips turned on by default which can cover it up though.
+#       Opera 7.0  - *Broken.*  Usually works, if the window is too narrow may collapse completely.
+#
+#       Konqueror 3.5  - OK
+#
+
+
+#
+#	Topic: Long code block scrolling
+#
+#	Go to <Prototype Parameter Styles>.
+#
+#	Tests:
+#
+#		- Shrink the browser window so that a line extends past the end of it.  Only the line should have a scrollbar, not the
+#		  entire page.
+#		- Expand the browser window.  The scrollbar should disappear.
+#
+#	Results:
+#
+#       Firefox 2.0  - OK
+#       Firefox 1.5  - OK
+#       Firefox 1.0  - OK
+#
+#       IE 7.0  - OK
+#       IE 6.0  - OK
+#
+#		Safari 3.0  - OK
+#		Safari 2.0  - OK
+#
+#       Opera 9.0  - OK
+#       Opera 8.5  - OK
+#       Opera 8.0  - OK
+#       Opera 7.5  - OK
+#       Opera 7.0  - OK
+#
+#       Konqueror 3.5  - OK
+#
+
+
+#
+#	Topic: Menu and Class Hierarchies
+#
+#	Go to <NaturalDocs::Languages::Simple>.
+#
+#	Tests:
+#
+#		- Class hierarchy should look okay.
+#		- Make sure the menu hierarchy opens up on its own when the page is loaded.
+#		- You should be able to click on groups to open and close them.
+#
+#	Results:
+#
+#       Firefox 2.0  - OK
+#       Firefox 1.5  - OK
+#       Firefox 1.0  - OK
+#
+#       IE 7.0  - OK
+#       IE 6.0  - OK
+#
+#		Safari 3.0  - OK
+#		Safari 2.0  - OK
+#
+#       Opera 9.0  - OK
+#       Opera 8.5  - OK
+#       Opera 8.0  - OK
+#       Opera 7.5  - OK
+#       Opera 7.0  - OK
+#
+#       Konqueror 3.5  - OK
+#
+
+
+1;

Added: trunk/build/NaturalDocs-1.4/Info/Languages.txt
===================================================================
--- trunk/build/NaturalDocs-1.4/Info/Languages.txt	                        (rev 0)
+++ trunk/build/NaturalDocs-1.4/Info/Languages.txt	2009-07-01 15:30:00 UTC (rev 4275)
@@ -0,0 +1,107 @@
+
+    Title: Language Notes
+_______________________________________________________________________________
+
+    This is more for my personal reference than anything else.
+
+
+    ___________________________________________________________________________
+
+    Topic: Prototype Parameter Styles
+    ___________________________________________________________________________
+
+    Parameters via Commas, Typed via Spaces:
+
+        > FunctionName ( type indentifier, type identifier = value, modifier type identifier )
+        > FunctionName ( indentifier, identifier = value )
+
+        The general idea is that parameters are separated by commas.  Identifiers cannot contain spaces.  Types and modifiers,
+        if available, are separated from the identifiers with spaces.  There may be an equals sign to set the default value.
+
+        So parsing means splitting by commas, stripping everything past an equals sign for the default value, stripping everything
+        after the last space for the identifier, and the rest is the type.  If there are no internal spaces after the default value is
+        stripped, it's all identifier.
+
+        Note that internal parenthesis, brackets, braces, and angle brackets should be parsed out.  They may be present in default
+        values or types and any commas and equal signs in them should not be included.
+
+        Applies to C++, Java, C#, JavaScript, Python, PHP, Ruby.
+
+        Applies to Perl as well, even though it doesn't have any real parameter declaration structure.  Just adding it with comments
+        is fine.
+
+    Parameters via Semicolons and Commas, Typed via Colons:
+
+        > FunctionName ( identifier: type; identifier, identifier: type; identifier: type := value )
+
+        Parameters via semicolons, types via colons.  However, there can be more than one parameter per type via commas.
+        Default values via colon-equals.
+
+        Applies to Pascal, Ada.
+
+
+    SQL:
+
+        > FunctionName ( identifier type, identifier modifier type, identifier type := value )
+
+        Parameters separated by commas.  Identifiers come before the types and are separated by a space.  Default values are
+        specified with colon-equals.
+
+        > FunctionName @identifier type, @dentifier modifier type, @identifier type = value
+
+        Microsoft's SQL uses equals instead of colon-equals, doesn't need parenthesis, and starts its parameter names with an @
+        symbol.
+
+
+    Visual Basic:
+
+        > FunctionName ( modifiers identifier as type, identifier = value )
+
+        Parameters separated by commas.  Default values via equals.  However, any number of modifiers may appear before the
+        identifier.  Those modifiers are ByVal, ByRef, Optional, and ParamArray.
+
+
+    Tcl:
+
+        > FunctionName { identifier identifier { whatever } } { code }
+
+        Identifiers are specified in the first set of braces and have no commas.  However, they can be broken out into sub-braces.
+
+
+    ___________________________________________________________________________
+
+    Topic: Syntax References
+    ___________________________________________________________________________
+
+    C++ - http://www.csci.csusb.edu/dick/c++std/syntax.html
+
+    C# - http://msdn.microsoft.com/library/default.asp?url=/library/en-us/csspec/html/CSharpSpecStart.asp.  Open in IE.
+
+    Java - http://cui.unige.ch/db-research/Enseignement/analyseinfo/
+    Ada - http://cui.unige.ch/db-research/Enseignement/analyseinfo/
+
+    SQL - http://cui.unige.ch/db-research/Enseignement/analyseinfo/,
+             <http://www.cs.umb.edu/cs634/ora9idocs/appdev.920/a96624/13_elems.htm>, or
+             <http://msdn.microsoft.com/library/default.asp?url=/library/en-us/tsqlref/ts_tsqlcon_6lyk.asp?frame=true> (open in IE).
+
+    JavaScript - http://academ.hvcc.edu/~kantopet/javascript/index.php
+
+    Python - http://www.python.org/doc/2.3.4/ref/ref.html
+
+    PHP - http://www.php.net/manual/en/langref.php
+
+    Visual Basic - http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vbls7/html/vbspecstart.asp.  Open in IE.
+
+    Pascal - <http://pages.cpsc.ucalgary.ca/~becker/231/SyntaxDiagrams/pascal-syntax_files/frame.htm>.  Open in IE.
+
+    Ruby - http://www.rubycentral.com/book/
+
+    ActionScript 2 - <http://download.macromedia.com/pub/documentation/en/flash/fl8/fl8_as2lr.pdf>
+    ActionScript 3 - <http://download.macromedia.com/pub/documentation/en/flex/2/prog_actionscript30.pdf>
+    E2X - http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-357.pdf
+
+    R - Somewhere on http://www.r-project.org.
+
+    ColdFusion - <http://livedocs.macromedia.com/coldfusion/6/Developing_ColdFusion_MX_Applications_with_CFML/contents.htm>
+
+    Eiffel - http://www.gobosoft.com/eiffel/syntax/

Added: trunk/build/NaturalDocs-1.4/Info/NDMarkup.txt
===================================================================
--- trunk/build/NaturalDocs-1.4/Info/NDMarkup.txt	                        (rev 0)
+++ trunk/build/NaturalDocs-1.4/Info/NDMarkup.txt	2009-07-01 15:30:00 UTC (rev 4275)
@@ -0,0 +1,91 @@
+
+    Architecture: NDMarkup
+_______________________________________________________________________________
+
+A markup format used by the parser, both internally and in <NaturalDocs::Parser::ParsedTopic> objects.  Text formatted in
+NDMarkup will only have the tags documented below.
+
+
+About: Top-Level Tags
+
+   All content will be surrounded by one of the top-level tags.  These tags will not appear within each other.
+
+   <p></p>         - Surrounds a paragraph.  Paragraph breaks will replace double line breaks, and single line breaks will
+                            be removed completely.
+
+   <code></code>   - Surrounds code or text diagrams that should appear literally in the output.
+
+   <h></h>         - Surrounds a heading.
+
+   <ul></ul>       - Surrounds a bulleted (unordered) list.
+   <dl></dl>       - Surrounds a description list, which is what you are reading.
+
+   <img mode="inline" target="" original=""> - An inline image.  Target contains the image target, and original contains the
+                                                                    original text in case it doesn't resolve.
+
+
+About: List Item Tags
+
+   These tags will only appear within their respective lists.
+
+   <li></li>       - Surrounds a bulleted list item.
+   <de></de>   - Surrounds a description list entry, which is the left side.  It will always be followed by a description list
+                         description.
+   <ds></ds>   - Surrounds a description list symbol.  This is the same as a description list entry, except that the content
+                         is also a referenceable symbol.  This occurs when inside a list topic.  This tag will always
+                         be followed by a description list description.
+   <dd></dd>   - Surrounds a description list description, which is the right side.  It will always be preceded by a description
+                         list entry or symbol.
+
+About: Text Tags
+
+   These tags will only appear in paragraphs, headings, or description list descriptions.
+
+   <b></b>         - Bold
+   <i></i>           - Italics
+   <u></u>         - Underline
+
+   <link target="" name="" original=""> - Surrounds a potential link to a symbol; potential because the target is not guaranteed to
+                                                            exist.  This tag merely designates an attempted link.  Target is what is attempting to be
+                                                            linked to, name is the text that should appear for a successful link, and original is the
+                                                            original text in case the link doesn't resolve.
+
+   <url target="" name="">                   - An external link.  There's no need for an original attribute because it will always be
+                                                             turned into an actual link.
+   <email target="" name="">               - A link to an e-mail address.
+
+   <img mode="link" target="" original=""> - An image link.  Target contains the image target, and original contains the original
+                                                                 text in case it doesn't resolve.
+
+
+About: Amp Chars
+
+   These are the only amp chars supported, and will appear everywhere.  Every other character will appear as is.
+
+   &amp;    - The ampersand &.
+   &quot;    - The double quote ".
+   &lt;        - The less than sign <.
+   &gt;       - The greater than sign >.
+
+About: Tabs
+
+    NDMarkup will not contain tab characters, only spaces.  Any tab characters appearing in the source files will be
+    expanded/replaced as necessary.
+
+
+About: General Tag Properties
+
+   Since the tags are generated, they will always have the following properties, which will make pattern matching much
+   easier.
+
+   - Tags and amp chars will always be in all lowercase.
+   - Properties will appear exactly as documented here.  They will be in all lowercase, in the documented order, and will have no
+     extraneous whitespace.  Anything appearing in the properties will have amp chars.
+   - All code is valid, meaning tags will always be closed, <li>s will only appear within <ul>s, etc.
+
+   So, for example, you can match description list entries with /<de>(.+?)<\/de>/ and $1 will be the text.  No surprises or
+   gotchas.  No need for sophisticated parsing routines.
+
+   Remember that for symbol definitions, the text should appear as is, but internally (such as for the anchor) they need to
+   be passed through <NaturalDocs::SymbolTable->Defines()> so that the output file is just as tolerant as
+   <NaturalDocs::SymbolTable>.

Added: trunk/build/NaturalDocs-1.4/Info/Symbol Management.txt
===================================================================
--- trunk/build/NaturalDocs-1.4/Info/Symbol Management.txt	                        (rev 0)
+++ trunk/build/NaturalDocs-1.4/Info/Symbol Management.txt	2009-07-01 15:30:00 UTC (rev 4275)
@@ -0,0 +1,59 @@
+
+    Architecture: Symbol Management
+
+####################################################################################
+
+    This is the architecture and code path for symbol management.  This is almost exclusively managed by <NaturalDocs::SymbolTable>, but it's complicated enough that I want a plain-English walk through of the code paths anyway.
+
+    An important thing to remember is that each section below is simplified initially and then expanded upon in later sections as more facets of the code are introduced.  You will not get the whole story of what a function does by reading just one section.
+
+
+
+    Topic: Symbol Storage
+    _______________________________________________________________________________________________________
+
+    Symbols are indexed primarily by their <SymbolString>, which is the normalized, pre-parsed series of identifiers that make it up.  A symbol can have any number of definitions, including none, but can only have one definition per file.  If a symbol is defined more than once in a file, only the first definition is counted.  Stored for each definition is the <TopicType>, summary, and prototype.
+
+    Each symbol that has a definition has one designated as the global definition.  This is the one linked to by other files, unless that file happens to have its own definition which then takes precedence.  Which definition is chosen is rather arbitrary at this point; probably the first one that got defined.  Similarly, if the global definition is deleted, which one is chosen to replace it is completely arbitrary.
+
+    Each symbol also stores a list of references to it.  Note that references can be interpreted as multiple symbols, and each of those symbols will store a link back to the reference.  In other words, every reference a symbol stores is one that _can_ be interpreted as that symbol, but that is not necessarily the interpretation the reference actually uses.  A reference could have a better interpretation it uses instead.
+
+    For example, suppose there are two functions, MyFunction() and MyClass.MyFunction().  The reference text "MyFunction()" appearing in MyClass can be interpreted as either MyClass.MyFunction(), or if that doesn't exist, the global MyFunction().  Both the symbols for MyFunction() and MyClass.MyFunction() will store that it's referenced by the link, even though the class scoped one serves as the actual definition.
+
+    This is also the reason a symbol can exist that has no definitions: it has references.  We want symbols to be created in the table for each reference interpretation, even if it doesn't exist.  These are called potential symbols.  The reason is so we know whether a new symbol definition fulfills an existing reference, since it may be a better interpretation for the reference than what is currently used.
+
+
+
+    Topic: Reference Storage
+    _______________________________________________________________________________________________________
+
+    References are indexed primarily by their <ReferenceString>, which is actually an elaborate data structure packed into a string.  It includes a <SymbolString> of the text that appears in the link and a bunch of other data that determines the rules by which the link can be resolved.  For example, it includes the scope it appears in and any "using" statements in effect, which are alternate possible scopes.  It includes the type of link it is (text links, the ones you explicitly put in comments, aren't the only kind) and resolving flags which encode the language-specific rules of non-text links.  But the bottom line is the <ReferenceString> encodes everything that influences how it may be resolved, so if two links come up with the same rules, they're considered two definitions of the same reference.  This is the understanding of the word "reference" that will used in this document.
+
+    Like symbols, each reference stores a list of definitions.  However, it only stores the name as all the other relevant information is encoded in the <ReferenceString> itself.  Unlike a symbol, which can be linked to the same no matter what kind of definitions it has, references that are in any way different might be interpreted differently and so need their own distinct entries in the symbol table.
+
+    References also store a list of interpretations.  Every possible interpretation of the reference is stored and given a numeric score.  The higher the score, the better it suits the reference.  In the MyFunction() example from before, MyClass.MyFunction() would have a higher score than just MyFunction() because the local scope should win.  Each interpretation has a unique score, there are no duplicates.
+
+    So the symbol and reference data structures are complimentary.  Each symbol has a list of every reference that might be interpreted as it, and every reference has a list of each symbol that it could be interpreted as.  Again, objects are created for potential symbols (those with references but no definitions) so that this structure always remains intact.
+
+    The interpretation with the highest score which actually exists is deemed the current interpretation of the reference.  Unlike symbols where the next global definition is arbitrary, the succession of reference interpretations is very controlled and predictable.
+
+
+    Topic: Change Detection
+    _______________________________________________________________________________________________________
+
+    Change management is handled a couple of ways.  First, there is a secondary file index in <NaturalDocs::SymbolTable> that stores which symbols and references are stored in each file.  It doesn't have any information other than a list of <SymbolStrings> and <ReferenceStrings> since they can be used in the main structures to look up the details.  If a file is deleted, the symbol table can then prune any definitions that should no longer be in the table.
+
+    Another way deals with how the information parsing stage works.  Files parsed for information just have their symbols and references added to the table regardless of whether this was the first time it was ever parsed or if it had been parsed before.  If it had been parsed before, all the information from the previous parse should be in the symbol table and file indexes already.  If a new symbol or reference is defined, that's fine, it's added to the table normally.  However, if a symbol is redefined it's ignored because only the first definition matters.  Also, this won't detect things that disappear.
+
+    Enter watched files.  <NaturalDocs::Parser> tells <NaturalDocs::SymbolTable> to designate a file as watched before it starts parsing it, and then says to analyze the changes when it's done.  The watched file is a second index of all the symbols and references that were defined since the watch started, including the specific details on the symbol definitions.  When the analysis is done, it compares the list of symbols and references to the one in the main file index.  Any that appear in the main file index but not the watched one are deleted because they didn't show up the second time around.  Any symbol definitions that are different in the watched file than the main file are changed to the former, since the first definition that appeared the second time around was different than the original.
+
+
+    Topic: Change Management
+    _______________________________________________________________________________________________________
+
+    When a symbol's global definition changes, either because it switches to another file or because the details of the current file's definition changed (prototype, summary, etc.) it goes through all the references that can be interpreted as that symbol, finds the ones that use it as their current definition, and marks all the files that define them for rebuilding.  The links in their output files have to be changed to the new definition or at least have their tooltips updated.
+
+    When a symbol's last definition is deleted, it goes through all the references that can be interpreted as that symbol, finds the ones that use it as their current definition, and has them reinterpreted to the definition with the next highest score.  The files that define them are also marked for rebuilding.
+
+    When a potential symbol's first definition is found, it goes through all the references that can be interpreted as it and sees if it can serve as a higher scored interpretation than the current one.  If so, the interpretations are changed and all the files that define them are marked for rebuilding.
+

Added: trunk/build/NaturalDocs-1.4/Info/images/Logo.png
===================================================================
(Binary files differ)


Property changes on: trunk/build/NaturalDocs-1.4/Info/images/Logo.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/build/NaturalDocs-1.4/JavaScript/NaturalDocs.js
===================================================================
--- trunk/build/NaturalDocs-1.4/JavaScript/NaturalDocs.js	                        (rev 0)
+++ trunk/build/NaturalDocs-1.4/JavaScript/NaturalDocs.js	2009-07-01 15:30:00 UTC (rev 4275)
@@ -0,0 +1,836 @@
+// This file is part of Natural Docs, which is Copyright (C) 2003-2008 Greg Valure
+// Natural Docs is licensed under the GPL
+
+
+//
+//  Browser Styles
+// ____________________________________________________________________________
+
+var agt=navigator.userAgent.toLowerCase();
+var browserType;
+var browserVer;
+
+if (agt.indexOf("opera") != -1)
+    {
+    browserType = "Opera";
+
+    if (agt.indexOf("opera 7") != -1 || agt.indexOf("opera/7") != -1)
+        {  browserVer = "Opera7";  }
+    else if (agt.indexOf("opera 8") != -1 || agt.indexOf("opera/8") != -1)
+        {  browserVer = "Opera8";  }
+    else if (agt.indexOf("opera 9") != -1 || agt.indexOf("opera/9") != -1)
+        {  browserVer = "Opera9";  }
+    }
+
+else if (agt.indexOf("applewebkit") != -1)
+    {
+    browserType = "Safari";
+
+    if (agt.indexOf("version/3") != -1)
+        {  browserVer = "Safari3";  }
+    else if (agt.indexOf("safari/4") != -1)
+        {  browserVer = "Safari2";  }
+    }
+
+else if (agt.indexOf("khtml") != -1)
+    {
+    browserType = "Konqueror";
+    }
+
+else if (agt.indexOf("msie") != -1)
+    {
+    browserType = "IE";
+
+    if (agt.indexOf("msie 6") != -1)
+        {  browserVer = "IE6";  }
+    else if (agt.indexOf("msie 7") != -1)
+        {  browserVer = "IE7";  }
+    }
+
+else if (agt.indexOf("gecko") != -1)
+    {
+    browserType = "Firefox";
+
+    if (agt.indexOf("rv:1.7") != -1)
+        {  browserVer = "Firefox1";  }
+    else if (agt.indexOf("rv:1.8)") != -1 || agt.indexOf("rv:1.8.0") != -1)
+        {  browserVer = "Firefox15";  }
+    else if (agt.indexOf("rv:1.8.1") != -1)
+        {  browserVer = "Firefox2";  }
+    }
+
+
+//
+//  Support Functions
+// ____________________________________________________________________________
+
+
+function GetXPosition(item)
+    {
+    var position = 0;
+
+    if (item.offsetWidth != null)
+        {
+        while (item != document.body && item != null)
+            {
+            position += item.offsetLeft;
+            item = item.offsetParent;
+            };
+        };
+
+    return position;
+    };
+
+
+function GetYPosition(item)
+    {
+    var position = 0;
+
+    if (item.offsetWidth != null)
+        {
+        while (item != document.body && item != null)
+            {
+            position += item.offsetTop;
+            item = item.offsetParent;
+            };
+        };
+
+    return position;
+    };
+
+
+function MoveToPosition(item, x, y)
+    {
+    // Opera 5 chokes on the px extension, so it can use the Microsoft one instead.
+
+    if (item.style.left != null)
+        {
+        item.style.left = x + "px";
+        item.style.top = y + "px";
+        }
+    else if (item.style.pixelLeft != null)
+        {
+        item.style.pixelLeft = x;
+        item.style.pixelTop = y;
+        };
+    };
+
+
+//
+//  Menu
+// ____________________________________________________________________________
+
+
+function ToggleMenu(id)
+    {
+    if (!window.document.getElementById)
+        {  return;  };
+
+    var display = window.document.getElementById(id).style.display;
+
+    if (display == "none")
+        {  display = "block";  }
+    else
+        {  display = "none";  }
+
+    window.document.getElementById(id).style.display = display;
+    }
+
+function HideAllBut(ids, max)
+    {
+    if (document.getElementById)
+        {
+        ids.sort( function(a,b) { return a - b; } );
+        var number = 1;
+
+        while (number < max)
+            {
+            if (ids.length > 0 && number == ids[0])
+                {  ids.shift();  }
+            else
+                {
+                document.getElementById("MGroupContent" + number).style.display = "none";
+                };
+
+            number++;
+            };
+        };
+    }
+
+
+//
+//  Tooltips
+// ____________________________________________________________________________
+
+
+var tooltipTimer = 0;
+
+function ShowTip(event, tooltipID, linkID)
+    {
+    if (tooltipTimer)
+        {  clearTimeout(tooltipTimer);  };
+
+    var docX = event.clientX + window.pageXOffset;
+    var docY = event.clientY + window.pageYOffset;
+
+    var showCommand = "ReallyShowTip('" + tooltipID + "', '" + linkID + "', " + docX + ", " + docY + ")";
+
+    tooltipTimer = setTimeout(showCommand, 1000);
+    }
+
+function ReallyShowTip(tooltipID, linkID, docX, docY)
+    {
+    tooltipTimer = 0;
+
+    var tooltip;
+    var link;
+
+    if (document.getElementById)
+        {
+        tooltip = document.getElementById(tooltipID);
+        link = document.getElementById(linkID);
+        }
+/*    else if (document.all)
+        {
+        tooltip = eval("document.all['" + tooltipID + "']");
+        link = eval("document.all['" + linkID + "']");
+        }
+*/
+    if (tooltip)
+        {
+        var left = GetXPosition(link);
+        var top = GetYPosition(link);
+        top += link.offsetHeight;
+
+
+        // The fallback method is to use the mouse X and Y relative to the document.  We use a separate if and test if its a number
+        // in case some browser snuck through the above if statement but didn't support everything.
+
+        if (!isFinite(top) || top == 0)
+            {
+            left = docX;
+            top = docY;
+            }
+
+        // Some spacing to get it out from under the cursor.
+
+        top += 10;
+
+        // Make sure the tooltip doesnt get smushed by being too close to the edge, or in some browsers, go off the edge of the
+        // page.  We do it here because Konqueror does get offsetWidth right even if it doesnt get the positioning right.
+
+        if (tooltip.offsetWidth != null)
+            {
+            var width = tooltip.offsetWidth;
+            var docWidth = document.body.clientWidth;
+
+            if (left + width > docWidth)
+                {  left = docWidth - width - 1;  }
+
+            // If there's a horizontal scroll bar we could go past zero because it's using the page width, not the window width.
+            if (left < 0)
+                {  left = 0;  };
+            }
+
+        MoveToPosition(tooltip, left, top);
+        tooltip.style.visibility = "visible";
+        }
+    }
+
+function HideTip(tooltipID)
+    {
+    if (tooltipTimer)
+        {
+        clearTimeout(tooltipTimer);
+        tooltipTimer = 0;
+        }
+
+    var tooltip;
+
+    if (document.getElementById)
+        {  tooltip = document.getElementById(tooltipID); }
+    else if (document.all)
+        {  tooltip = eval("document.all['" + tooltipID + "']");  }
+
+    if (tooltip)
+        {  tooltip.style.visibility = "hidden";  }
+    }
+
+
+//
+//  Blockquote fix for IE
+// ____________________________________________________________________________
+
+
+function NDOnLoad()
+    {
+    if (browserVer == "IE6")
+        {
+        var scrollboxes = document.getElementsByTagName('blockquote');
+
+        if (scrollboxes.item(0))
+            {
+            NDDoResize();
+            window.onresize=NDOnResize;
+            };
+        };
+    };
+
+
+var resizeTimer = 0;
+
+function NDOnResize()
+    {
+    if (resizeTimer != 0)
+        {  clearTimeout(resizeTimer);  };
+
+    resizeTimer = setTimeout(NDDoResize, 250);
+    };
+
+
+function NDDoResize()
+    {
+    var scrollboxes = document.getElementsByTagName('blockquote');
+
+    var i;
+    var item;
+
+    i = 0;
+    while (item = scrollboxes.item(i))
+        {
+        item.style.width = 100;
+        i++;
+        };
+
+    i = 0;
+    while (item = scrollboxes.item(i))
+        {
+        item.style.width = item.parentNode.offsetWidth;
+        i++;
+        };
+
+    clearTimeout(resizeTimer);
+    resizeTimer = 0;
+    }
+
+
+
+/* ________________________________________________________________________________________________________
+
+    Class: SearchPanel
+    ________________________________________________________________________________________________________
+
+    A class handling everything associated with the search panel.
+
+    Parameters:
+
+        name - The name of the global variable that will be storing this instance.  Is needed to be able to set timeouts.
+        mode - The mode the search is going to work in.  Pass <NaturalDocs::Builder::Base->CommandLineOption()>, so the
+                   value will be something like "HTML" or "FramedHTML".
+
+    ________________________________________________________________________________________________________
+*/
+
+
+function SearchPanel(name, mode, resultsPath)
+    {
+    if (!name || !mode || !resultsPath)
+        {  alert("Incorrect parameters to SearchPanel.");  };
+
+
+    // Group: Variables
+    // ________________________________________________________________________
+
+    /*
+        var: name
+        The name of the global variable that will be storing this instance of the class.
+    */
+    this.name = name;
+
+    /*
+        var: mode
+        The mode the search is going to work in, such as "HTML" or "FramedHTML".
+    */
+    this.mode = mode;
+
+    /*
+        var: resultsPath
+        The relative path from the current HTML page to the results page directory.
+    */
+    this.resultsPath = resultsPath;
+
+    /*
+        var: keyTimeout
+        The timeout used between a keystroke and when a search is performed.
+    */
+    this.keyTimeout = 0;
+
+    /*
+        var: keyTimeoutLength
+        The length of <keyTimeout> in thousandths of a second.
+    */
+    this.keyTimeoutLength = 500;
+
+    /*
+        var: lastSearchValue
+        The last search string executed, or an empty string if none.
+    */
+    this.lastSearchValue = "";
+
+    /*
+        var: lastResultsPage
+        The last results page.  The value is only relevant if <lastSearchValue> is set.
+    */
+    this.lastResultsPage = "";
+
+    /*
+        var: deactivateTimeout
+
+        The timeout used between when a control is deactivated and when the entire panel is deactivated.  Is necessary
+        because a control may be deactivated in favor of another control in the same panel, in which case it should stay
+        active.
+    */
+    this.deactivateTimout = 0;
+
+    /*
+        var: deactivateTimeoutLength
+        The length of <deactivateTimeout> in thousandths of a second.
+    */
+    this.deactivateTimeoutLength = 200;
+
+
+
+
+    // Group: DOM Elements
+    // ________________________________________________________________________
+
+
+    // Function: DOMSearchField
+    this.DOMSearchField = function()
+        {  return document.getElementById("MSearchField");  };
+
+    // Function: DOMSearchType
+    this.DOMSearchType = function()
+        {  return document.getElementById("MSearchType");  };
+
+    // Function: DOMPopupSearchResults
+    this.DOMPopupSearchResults = function()
+        {  return document.getElementById("MSearchResults");  };
+
+    // Function: DOMPopupSearchResultsWindow
+    this.DOMPopupSearchResultsWindow = function()
+        {  return document.getElementById("MSearchResultsWindow");  };
+
+    // Function: DOMSearchPanel
+    this.DOMSearchPanel = function()
+        {  return document.getElementById("MSearchPanel");  };
+
+
+
+
+    // Group: Event Handlers
+    // ________________________________________________________________________
+
+
+    /*
+        Function: OnSearchFieldFocus
+        Called when focus is added or removed from the search field.
+    */
+    this.OnSearchFieldFocus = function(isActive)
+        {
+        this.Activate(isActive);
+        };
+
+
+    /*
+        Function: OnSearchFieldChange
+        Called when the content of the search field is changed.
+    */
+    this.OnSearchFieldChange = function()
+        {
+        if (this.keyTimeout)
+            {
+            clearTimeout(this.keyTimeout);
+            this.keyTimeout = 0;
+            };
+
+        var searchValue = this.DOMSearchField().value.replace(/ +/g, "");
+
+        if (searchValue != this.lastSearchValue)
+            {
+            if (searchValue != "")
+                {
+                this.keyTimeout = setTimeout(this.name + ".Search()", this.keyTimeoutLength);
+                }
+            else
+                {
+                if (this.mode == "HTML")
+                    {  this.DOMPopupSearchResultsWindow().style.display = "none";  };
+                this.lastSearchValue = "";
+                };
+            };
+        };
+
+
+    /*
+        Function: OnSearchTypeFocus
+        Called when focus is added or removed from the search type.
+    */
+    this.OnSearchTypeFocus = function(isActive)
+        {
+        this.Activate(isActive);
+        };
+
+
+    /*
+        Function: OnSearchTypeChange
+        Called when the search type is changed.
+    */
+    this.OnSearchTypeChange = function()
+        {
+        var searchValue = this.DOMSearchField().value.replace(/ +/g, "");
+
+        if (searchValue != "")
+            {
+            this.Search();
+            };
+        };
+
+
+
+    // Group: Action Functions
+    // ________________________________________________________________________
+
+
+    /*
+        Function: CloseResultsWindow
+        Closes the results window.
+    */
+    this.CloseResultsWindow = function()
+        {
+        this.DOMPopupSearchResultsWindow().style.display = "none";
+        this.Activate(false, true);
+        };
+
+
+    /*
+        Function: Search
+        Performs a search.
+    */
+    this.Search = function()
+        {
+        this.keyTimeout = 0;
+
+        var searchValue = this.DOMSearchField().value.replace(/^ +/, "");
+        var searchTopic = this.DOMSearchType().value;
+
+        var pageExtension = searchValue.substr(0,1);
+
+        if (pageExtension.match(/^[a-z]/i))
+            {  pageExtension = pageExtension.toUpperCase();  }
+        else if (pageExtension.match(/^[0-9]/))
+            {  pageExtension = 'Numbers';  }
+        else
+            {  pageExtension = "Symbols";  };
+
+        var resultsPage;
+        var resultsPageWithSearch;
+        var hasResultsPage;
+
+        // indexSectionsWithContent is defined in searchdata.js
+        if (indexSectionsWithContent[searchTopic][pageExtension] == true)
+            {
+            resultsPage = this.resultsPath + '/' + searchTopic + pageExtension + '.html';
+            resultsPageWithSearch = resultsPage+'?'+escape(searchValue);
+            hasResultsPage = true;
+            }
+        else
+            {
+            resultsPage = this.resultsPath + '/NoResults.html';
+            resultsPageWithSearch = resultsPage;
+            hasResultsPage = false;
+            };
+
+        var resultsFrame;
+        if (this.mode == "HTML")
+            {  resultsFrame = window.frames.MSearchResults;  }
+        else if (this.mode == "FramedHTML")
+            {  resultsFrame = window.top.frames['Content'];  };
+
+
+        if (resultsPage != this.lastResultsPage ||
+
+            // Bug in IE.  If everything becomes hidden in a run, none of them will be able to be reshown in the next for some
+            // reason.  It counts the right number of results, and you can even read the display as "block" after setting it, but it
+            // just doesn't work in IE 6 or IE 7.  So if we're on the right page but the previous search had no results, reload the
+            // page anyway to get around the bug.
+            (browserType == "IE" && hasResultsPage &&
+            	(!resultsFrame.searchResults || resultsFrame.searchResults.lastMatchCount == 0)) )
+
+            {
+            resultsFrame.location.href = resultsPageWithSearch;
+            }
+
+        // So if the results page is right and there's no IE bug, reperform the search on the existing page.  We have to check if there
+        // are results because NoResults.html doesn't have any JavaScript, and it would be useless to do anything on that page even
+        // if it did.
+        else if (hasResultsPage)
+            {
+            // We need to check if this exists in case the frame is present but didn't finish loading.
+            if (resultsFrame.searchResults)
+                {  resultsFrame.searchResults.Search(searchValue);  }
+
+            // Otherwise just reload instead of waiting.
+            else
+                {  resultsFrame.location.href = resultsPageWithSearch;  };
+            };
+
+
+        var domPopupSearchResultsWindow = this.DOMPopupSearchResultsWindow();
+
+        if (this.mode == "HTML" && domPopupSearchResultsWindow.style.display != "block")
+            {
+            var domSearchType = this.DOMSearchType();
+
+            var left = GetXPosition(domSearchType);
+            var top = GetYPosition(domSearchType) + domSearchType.offsetHeight;
+
+            MoveToPosition(domPopupSearchResultsWindow, left, top);
+            domPopupSearchResultsWindow.style.display = 'block';
+            };
+
+
+        this.lastSearchValue = searchValue;
+        this.lastResultsPage = resultsPage;
+        };
+
+
+
+    // Group: Activation Functions
+    // Functions that handle whether the entire panel is active or not.
+    // ________________________________________________________________________
+
+
+    /*
+        Function: Activate
+
+        Activates or deactivates the search panel, resetting things to their default values if necessary.  You can call this on every
+        control's OnBlur() and it will handle not deactivating the entire panel when focus is just switching between them transparently.
+
+        Parameters:
+
+            isActive - Whether you're activating or deactivating the panel.
+            ignoreDeactivateDelay - Set if you're positive the action will deactivate the panel and thus want to skip the delay.
+    */
+    this.Activate = function(isActive, ignoreDeactivateDelay)
+        {
+        // We want to ignore isActive being false while the results window is open.
+        if (isActive || (this.mode == "HTML" && this.DOMPopupSearchResultsWindow().style.display == "block"))
+            {
+            if (this.inactivateTimeout)
+                {
+                clearTimeout(this.inactivateTimeout);
+                this.inactivateTimeout = 0;
+                };
+
+            this.DOMSearchPanel().className = 'MSearchPanelActive';
+
+            var searchField = this.DOMSearchField();
+
+            if (searchField.value == 'Search')
+                 {  searchField.value = "";  }
+            }
+        else if (!ignoreDeactivateDelay)
+            {
+            this.inactivateTimeout = setTimeout(this.name + ".InactivateAfterTimeout()", this.inactivateTimeoutLength);
+            }
+        else
+            {
+            this.InactivateAfterTimeout();
+            };
+        };
+
+
+    /*
+        Function: InactivateAfterTimeout
+
+        Called by <inactivateTimeout>, which is set by <Activate()>.  Inactivation occurs on a timeout because a control may
+        receive OnBlur() when focus is really transferring to another control in the search panel.  In this case we don't want to
+        actually deactivate the panel because not only would that cause a visible flicker but it could also reset the search value.
+        So by doing it on a timeout instead, there's a short period where the second control's OnFocus() can cancel the deactivation.
+    */
+    this.InactivateAfterTimeout = function()
+        {
+        this.inactivateTimeout = 0;
+
+        this.DOMSearchPanel().className = 'MSearchPanelInactive';
+        this.DOMSearchField().value = "Search";
+
+	    this.lastSearchValue = "";
+	    this.lastResultsPage = "";
+        };
+    };
+
+
+
+
+/* ________________________________________________________________________________________________________
+
+   Class: SearchResults
+   _________________________________________________________________________________________________________
+
+   The class that handles everything on the search results page.
+   _________________________________________________________________________________________________________
+*/
+
+
+function SearchResults(name, mode)
+    {
+    /*
+        var: mode
+        The mode the search is going to work in, such as "HTML" or "FramedHTML".
+    */
+    this.mode = mode;
+
+    /*
+        var: lastMatchCount
+        The number of matches from the last run of <Search()>.
+    */
+    this.lastMatchCount = 0;
+
+
+    /*
+        Function: Toggle
+        Toggles the visibility of the passed element ID.
+    */
+    this.Toggle = function(id)
+        {
+        if (this.mode == "FramedHTML")
+            {  return;  };
+
+        var parentElement = document.getElementById(id);
+
+        var element = parentElement.firstChild;
+
+        while (element && element != parentElement)
+            {
+            if (element.nodeName == 'DIV' && element.className == 'ISubIndex')
+                {
+                if (element.style.display == 'block')
+                    {  element.style.display = "none";  }
+                else
+                    {  element.style.display = 'block';  }
+                };
+
+            if (element.nodeName == 'DIV' && element.hasChildNodes())
+                {  element = element.firstChild;  }
+            else if (element.nextSibling)
+                {  element = element.nextSibling;  }
+            else
+                {
+                do
+                    {
+                    element = element.parentNode;
+                    }
+                while (element && element != parentElement && !element.nextSibling);
+
+                if (element && element != parentElement)
+                    {  element = element.nextSibling;  };
+                };
+            };
+        };
+
+
+    /*
+        Function: Search
+
+        Searches for the passed string.  If there is no parameter, it takes it from the URL query.
+
+        Always returns true, since other documents may try to call it and that may or may not be possible.
+    */
+    this.Search = function(search)
+        {
+        if (!search)
+            {
+            search = window.location.search;
+            search = search.substring(1);  // Remove the leading ?
+            search = unescape(search);
+            };
+
+        search = search.replace(/^ +/, "");
+        search = search.replace(/ +$/, "");
+        search = search.toLowerCase();
+
+        if (search.match(/[^a-z0-9]/)) // Just a little speedup so it doesn't have to go through the below unnecessarily.
+            {
+            search = search.replace(/\_/g, "_und");
+            search = search.replace(/\ +/gi, "_spc");
+            search = search.replace(/\~/g, "_til");
+            search = search.replace(/\!/g, "_exc");
+            search = search.replace(/\@/g, "_att");
+            search = search.replace(/\#/g, "_num");
+            search = search.replace(/\$/g, "_dol");
+            search = search.replace(/\%/g, "_pct");
+            search = search.replace(/\^/g, "_car");
+            search = search.replace(/\&/g, "_amp");
+            search = search.replace(/\*/g, "_ast");
+            search = search.replace(/\(/g, "_lpa");
+            search = search.replace(/\)/g, "_rpa");
+            search = search.replace(/\-/g, "_min");
+            search = search.replace(/\+/g, "_plu");
+            search = search.replace(/\=/g, "_equ");
+            search = search.replace(/\{/g, "_lbc");
+            search = search.replace(/\}/g, "_rbc");
+            search = search.replace(/\[/g, "_lbk");
+            search = search.replace(/\]/g, "_rbk");
+            search = search.replace(/\:/g, "_col");
+            search = search.replace(/\;/g, "_sco");
+            search = search.replace(/\"/g, "_quo");
+            search = search.replace(/\'/g, "_apo");
+            search = search.replace(/\</g, "_lan");
+            search = search.replace(/\>/g, "_ran");
+            search = search.replace(/\,/g, "_com");
+            search = search.replace(/\./g, "_per");
+            search = search.replace(/\?/g, "_que");
+            search = search.replace(/\//g, "_sla");
+            search = search.replace(/[^a-z0-9\_]i/gi, "_zzz");
+            };
+
+        var resultRows = document.getElementsByTagName("div");
+        var matches = 0;
+
+        var i = 0;
+        while (i < resultRows.length)
+            {
+            var row = resultRows.item(i);
+
+            if (row.className == "SRResult")
+                {
+                var rowMatchName = row.id.toLowerCase();
+                rowMatchName = rowMatchName.replace(/^sr\d*_/, '');
+
+                if (search.length <= rowMatchName.length && rowMatchName.substr(0, search.length) == search)
+                    {
+                    row.style.display = "block";
+                    matches++;
+                    }
+                else
+                    {  row.style.display = "none";  };
+                };
+
+            i++;
+            };
+
+        document.getElementById("Searching").style.display="none";
+
+        if (matches == 0)
+            {  document.getElementById("NoMatches").style.display="block";  }
+        else
+            {  document.getElementById("NoMatches").style.display="none";  }
+
+        this.lastMatchCount = matches;
+
+        return true;
+        };
+    };
+

Added: trunk/build/NaturalDocs-1.4/License-GPL.txt
===================================================================
--- trunk/build/NaturalDocs-1.4/License-GPL.txt	                        (rev 0)
+++ trunk/build/NaturalDocs-1.4/License-GPL.txt	2009-07-01 15:30:00 UTC (rev 4275)
@@ -0,0 +1,341 @@
+File: GNU General Public Licence
+
+Version 2, June 1991
+
+Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+Everyone is permitted to copy and distribute verbatim copies
+of this license document, but changing it is not allowed.
+
+
+Topic: Preamble
+
+The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.)  You can apply it to
+your programs, too.
+
+When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+The precise terms and conditions for copying, distribution and
+modification follow.
+
+
+Topic: Terms and Conditions for Copying, Distribution, and Modification
+
+0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+a) You must cause the modified files to carry prominent notices
+stating that you changed the files and the date of any change.
+
+b) You must cause any work that you distribute or publish, that in
+whole or in part contains or is derived from the Program or any
+part thereof, to be licensed as a whole at no charge to all third
+parties under the terms of this License.
+
+c) If the modified program normally reads commands interactively
+when run, you must cause it, when started running for such
+interactive use in the most ordinary way, to print or display an
+announcement including an appropriate copyright notice and a
+notice that there is no warranty (or else, saying that you provide
+a warranty) and that users may redistribute the program under
+these conditions, and telling the user how to view a copy of this
+License.  (Exception: if the Program itself is interactive but
+does not normally print such an announcement, your work based on
+the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+a) Accompany it with the complete corresponding machine-readable
+source code, which must be distributed under the terms of Sections
+1 and 2 above on a medium customarily used for software interchange; or,
+
+b) Accompany it with a written offer, valid for at least three
+years, to give any third party, for a charge no more than your
+cost of physically performing source distribution, a complete
+machine-readable copy of the corresponding source code, to be
+distributed under the terms of Sections 1 and 2 above on a medium
+customarily used for software interchange; or,
+
+c) Accompany it with the information you received as to the offer
+to distribute corresponding source code.  (This alternative is
+allowed only for noncommercial distribution and only if you
+received the program in object code or executable form with such
+an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+NO WARRANTY:
+
+11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+
+Topic: How to Apply These Terms to Your New Programs
+
+If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+<one line to give the program's name and a brief idea of what it does.>
+Copyright (C) <year>  <name of author>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+Gnomovision version 69, Copyright (C) year name of author
+Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+This is free software, and you are welcome to redistribute it
+under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+`Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+<signature of Ty Coon>, 1 April 1989
+Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Library General
+Public License instead of this License.

Added: trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/BinaryFile.pm
===================================================================
--- trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/BinaryFile.pm	                        (rev 0)
+++ trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/BinaryFile.pm	2009-07-01 15:30:00 UTC (rev 4275)
@@ -0,0 +1,294 @@
+###############################################################################
+#
+#   Package: NaturalDocs::BinaryFile
+#
+###############################################################################
+#
+#   A package to manage Natural Docs' binary data files.
+#
+#   Usage:
+#
+#       - Only one data file can be managed with this package at a time.  You must close the file before opening another
+#         one.
+#
+###############################################################################
+
+# 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::BinaryFile;
+
+use vars qw(@EXPORT @ISA);
+require Exporter;
+ at ISA = qw(Exporter);
+
+ at EXPORT = ('BINARY_FORMAT');
+
+
+###############################################################################
+# Group: Format
+
+#
+#   Topic: Standard Header
+#
+#   > [UInt8: BINARY_FORMAT]
+#   > [VersionInt: app version]
+#
+#   The first byte is <BINARY_FORMAT>, which distinguishes binary configuration files from text ones, since Natural Docs
+#   used to use text data files with the same name.
+#
+#   The next section is the version of Natural Docs that wrote the file, as defined by <NaturalDocs::Settings->AppVersion>
+#   and written by <NaturalDocs::Version->ToBinaryFile()>.
+#
+
+#
+#   Topic: Data Types
+#
+#   All the integer data types are written most significant byte first, aka big endian.
+#
+#   An AString16 is a UInt16 followed by that many 8-bit ASCII characters.  It doesn't include a null character at the end.  Undef
+#   strings are represented by a zero for the UInt16 and nothing following it.
+#
+
+#
+#   Constant: BINARY_FORMAT
+#
+#   An 8-bit constant that's used as the first byte of binary data files.  This is used so that you can easily distinguish between
+#   binary and old-style text data files.  It's not a character that would appear in plain text files.
+#
+use constant BINARY_FORMAT => pack('C', 0x06);
+# Which is ACK or acknowledge in ASCII.  Is the cool spade character in DOS displays.
+
+
+###############################################################################
+# Group: Variables
+
+#
+#   handle: FH_BINARYDATAFILE
+#
+#   The file handle used for the data file.
+#
+
+
+#
+#   string: currentFile
+#
+#   The <FileName> for the current configuration file being parsed.
+#
+my $currentFile;
+
+
+
+###############################################################################
+# Group: File Functions
+
+
+#
+#   Function: OpenForReading
+#
+#   Opens a binary file for reading.
+#
+#   Parameters:
+#
+#       minimumVersion - The minimum version of the file format that is acceptible.  May be undef.
+#
+#   Returns:
+#
+#       The format <VersionInt> or undef if it failed.  It could fail for any of the following reasons.
+#
+#       - The file doesn't exist.
+#       - The file couldn't be opened.
+#       - The file didn't have the proper header.
+#       - Either the application or the file was from a development release, and they're not the exact same development release.
+#       - The file's format was less than the minimum version, if one was defined.
+#       - The file was from a later application version than the current.
+#
+sub OpenForReading #(FileName file, optional VersionInt minimumVersion) => VersionInt
+    {
+    my ($self, $file, $minimumVersion) = @_;
+
+    if (defined $currentFile)
+        {  die "Tried to open binary file " . $file . " for reading when " . $currentFile . " was already open.";  };
+
+    $currentFile = $file;
+
+    if (open(FH_BINARYDATAFILE, '<' . $currentFile))
+        {
+        # See if it's binary.
+        binmode(FH_BINARYDATAFILE);
+
+        my $firstChar;
+        read(FH_BINARYDATAFILE, $firstChar, 1);
+
+        if ($firstChar == ::BINARY_FORMAT())
+            {
+            my $version = NaturalDocs::Version->FromBinaryFile(\*FH_BINARYDATAFILE);
+
+            if (NaturalDocs::Version->CheckFileFormat($version, $minimumVersion))
+                {  return $version;  };
+            };
+
+        close(FH_BINARYDATAFILE);
+        };
+
+    $currentFile = undef;
+    return undef;
+    };
+
+
+#
+#   Function: OpenForWriting
+#
+#   Opens a binary file for writing and writes the standard header.  Dies if the file cannot be opened.
+#
+sub OpenForWriting #(FileName file)
+    {
+    my ($self, $file) = @_;
+
+    if (defined $currentFile)
+        {  die "Tried to open binary file " . $file . " for writing when " . $currentFile . " was already open.";  };
+
+    $currentFile = $file;
+
+    open (FH_BINARYDATAFILE, '>' . $currentFile)
+        or die "Couldn't save " . $file . ".\n";
+
+    binmode(FH_BINARYDATAFILE);
+
+    print FH_BINARYDATAFILE '' . ::BINARY_FORMAT();
+    NaturalDocs::Version->ToBinaryFile(\*FH_BINARYDATAFILE, NaturalDocs::Settings->AppVersion());
+    };
+
+
+#
+#   Function: Close
+#
+#   Closes the current configuration file.
+#
+sub Close
+    {
+    my $self = shift;
+
+    if (!$currentFile)
+        {  die "Tried to close a binary file when one wasn't open.";  };
+
+    close(FH_BINARYDATAFILE);
+    $currentFile = undef;
+    };
+
+
+
+###############################################################################
+# Group: Reading Functions
+
+
+#
+#   Function: GetUInt8
+#   Reads and returns a UInt8 from the open file.
+#
+sub GetUInt8 # => UInt8
+    {
+    my $raw;
+    read(FH_BINARYDATAFILE, $raw, 1);
+
+    return unpack('C', $raw);
+    };
+
+#
+#   Function: GetUInt16
+#   Reads and returns a UInt16 from the open file.
+#
+sub GetUInt16 # => UInt16
+    {
+    my $raw;
+    read(FH_BINARYDATAFILE, $raw, 2);
+
+    return unpack('n', $raw);
+    };
+
+#
+#   Function: GetUInt32
+#   Reads and returns a UInt32 from the open file.
+#
+sub GetUInt32 # => UInt32
+    {
+    my $raw;
+    read(FH_BINARYDATAFILE, $raw, 4);
+
+    return unpack('N', $raw);
+    };
+
+#
+#   Function: GetAString16
+#   Reads and returns an AString16 from the open file.  Supports undef strings.
+#
+sub GetAString16 # => string
+    {
+    my $rawLength;
+    read(FH_BINARYDATAFILE, $rawLength, 2);
+    my $length = unpack('n', $rawLength);
+
+    if (!$length)
+        {  return undef;  };
+
+    my $string;
+    read(FH_BINARYDATAFILE, $string, $length);
+
+    return $string;
+    };
+
+
+
+###############################################################################
+# Group: Writing Functions
+
+
+#
+#   Function: WriteUInt8
+#   Writes a UInt8 to the open file.
+#
+sub WriteUInt8 #(UInt8 value)
+    {
+    my ($self, $value) = @_;
+    print FH_BINARYDATAFILE pack('C', $value);
+    };
+
+#
+#   Function: WriteUInt16
+#   Writes a UInt32 to the open file.
+#
+sub WriteUInt16 #(UInt16 value)
+    {
+    my ($self, $value) = @_;
+    print FH_BINARYDATAFILE pack('n', $value);
+    };
+
+#
+#   Function: WriteUInt32
+#   Writes a UInt32 to the open file.
+#
+sub WriteUInt32 #(UInt32 value)
+    {
+    my ($self, $value) = @_;
+    print FH_BINARYDATAFILE pack('N', $value);
+    };
+
+#
+#   Function: WriteAString16
+#   Writes an AString16 to the open file.  Supports undef strings.
+#
+sub WriteAString16 #(string value)
+    {
+    my ($self, $string) = @_;
+
+    if (length($string))
+        {  print FH_BINARYDATAFILE pack('nA*', length($string), $string);  }
+    else
+        {  print FH_BINARYDATAFILE pack('n', 0);  };
+    };
+
+
+1;

Added: trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Builder/Base.pm
===================================================================
--- trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Builder/Base.pm	                        (rev 0)
+++ trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Builder/Base.pm	2009-07-01 15:30:00 UTC (rev 4275)
@@ -0,0 +1,348 @@
+###############################################################################
+#
+#   Class: NaturalDocs::Builder::Base
+#
+###############################################################################
+#
+#   A base class for all Builder output formats.
+#
+###############################################################################
+
+# 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::Builder::Base;
+
+
+###############################################################################
+# Group: Notes
+
+
+#
+#   Topic: Implementation
+#
+#   Builder packages are implemented as blessed arrayrefs, not hashrefs.  This is done for all objects in Natural Docs for
+#   efficiency reasons.  You create members by defining constants via <NaturalDocs::DefineMembers> and using them as
+#   indexes into the array.
+#
+
+#
+#   Topic: Function Order
+#
+#   The functions in the build process will always be called in the following order.
+#
+#   - <BeginBuild()> will always be called.
+#   - <PurgeFiles()> will be called next only if there's files that need to be purged.
+#   - <PurgeIndexes()> will be called next only if there's indexes that need to be purged.
+#   - <PurgeImages()> will e called next only if there's images that need to be purged.
+#   - <BuildFile()> will be called once for each file that needs to be built, if any.
+#   - <BuildIndex()> will be called once for each index that changed and is part of the menu, if any.
+#   - <UpdateImage()> will be called once for each image that needs to be updated, if any.
+#   - <UpdateMenu()> will be called next only if the menu changed.
+#   - <EndBuild()> will always be called.
+#
+
+#
+#   Topic: How to Approach
+#
+#   Here's an idea of how to approach making packages for different output types.
+#
+#
+#   Multiple Output Files, Embedded Menu:
+#
+#       This example is for when you want to build one output file per source file, each with its own copy of the menu within it.
+#       This is how <NaturalDocs::Builder::HTML> works.
+#
+#       Make sure you create a function that generates just the menu for a particular source file.  We'll need to generate menus for
+#       both building a file from scratch and for updating the menu on an existing output file, so it's better to give it its own function.
+#       You may want to surround it with something that can be easily detected in the output file to make replacing easier.
+#
+#       <BeginBuild()> isn't important.  You don't need to implement it.
+#
+#       Implement <PurgeFiles()> to delete the output files associated with the purged files.
+#
+#       Implement <PurgeIndexes()> to delete the output files associated with the purged indexes.
+#
+#       Implement <BuildFile()> to create an output file for the parsed source file.  Use the menu function described earlier.
+#
+#       Implement <BuildIndex()> to create an output file for each index.  Use the menu function described earlier for each page.
+#
+#       Implement <UpdateMenu()> to go through the list of unbuilt files and update their menus.  You can get the list from
+#       <NaturalDocs::Project->UnbuiltFilesWithContent()>.  You need to open their output files, replace the menu, and save it back
+#       to disk.  Yes, it would be simpler from a programmer's point of view to just rebuild the file completely, but that would be
+#       _very_ inefficient since there could potentially be a _lot_ of files in this group.
+#
+#       Also make sure <UpdateMenu()> goes through the unchanged indexes and updates them as well.
+#
+#       <EndBuild()> isn't important.  You don't need to implement it.
+#
+#
+#   Multiple Output Files, Menu in File:
+#
+#       This example is for when you want to build one output file per source file, but keep the menu in its own separate file.  This
+#       is how <NaturalDocs::Builder::FramedHTML> works.
+#
+#       <BeginBuild()> isn't important.  You don't need to implement it.
+#
+#       Implement <PurgeFiles()> to delete the output files associated with the purged files.
+#
+#       Implement <PurgeIndexes()> to delete the output files associated with the purged indexes.
+#
+#       Implement <BuildFile()> to generate an output file from the parsed source file.
+#
+#       Implement <BuildIndex()> to generate an output file for each index.
+#
+#       Implement <UpdateMenu()> to rebuild the menu file.
+#
+#       <EndBuild()> isn't important.  You don't need to implement it.
+#
+#
+#   Single Output File using Intermediate Files:
+#
+#       This example is for when you want to build one output file, such as a PDF file, but use intermediate files to handle differential
+#       building.  This would be much like how a compiler compiles each source file into a object file, and then a linker stitches them
+#       all together into the final executable file.
+#
+#       <BeginBuild()> isn't important.  You don't need to implement it.
+#
+#       Implement <PurgeFiles()> to delete the intermediate files associated with the purged files.
+#
+#       Implement <PurgeIndexes()> to delete the intermediate files associated with the purged indexes.
+#
+#       Implement <BuildFile()> to generate an intermediate file from the parsed source file.
+#
+#       Implement <BuildIndex()> to generate an intermediate file for the specified index.
+#
+#       Implement <UpdateMenu()> to generate the intermediate file for the menu.
+#
+#       Implement <EndBuild()> so that if the project changed, it stitches the intermediate files together into the final
+#       output file.  Make sure you check the parameter because the function will be called when nothing changes too.
+#
+#
+#   Single Output File using Direct Changes:
+#
+#       This example is for when you want to build one output file, such as a PDF file, but engineering it in such a way that you don't
+#       need to use intermediate files.  In other words, you're able to add, delete, and modify entries directly in the output file.
+#
+#       Implement <BeginBuild()> so that if the project changed, it opens the output file and does anything it needs to do
+#       to get ready for editing.
+#
+#       Implement <PurgeFiles()> to remove the entries associated with the purged files.
+#
+#       Implement <PurgeIndexes()> to remove the entries associated with the purged indexes.
+#
+#       Implement <BuildFile()> to add or replace a section of the output file with a new one generated from the parsed file.
+#
+#       Implement <BuildIndex()> to add or replace an index in the output file with a new one generated from the specified index.
+#
+#       Implement <EndBuild()> so that if the project changed, it saves the output file to disk.
+#
+#       How you handle the menu depends on how the output file references other sections of itself.  If it can do so by name, then
+#       you can implement <UpdateMenu()> to update the menu section of the file and you're done.  If it has to reference itself
+#       by address or offset, it gets trickier.  You should skip <UpdateMenu()> and instead rebuild the menu in <EndBuild()> if
+#       the parameter is true.  This lets you do it whenever anything changes in a file, rather than just when the menu
+#       visibly changes.  How you keep track of the locations and how they change is your problem.
+#
+
+
+###############################################################################
+#
+#   Group: Required Interface Functions
+#
+#   All Builder classes *must* define these functions.
+#
+
+
+#
+#   Function: INIT
+#
+#   Define this function to call <NaturalDocs::Builder->Add()> so that <NaturalDocs::Builder> knows about this package.
+#   Packages are defined this way so that new ones can be added without messing around in other code.
+#
+
+
+#
+#   Function: CommandLineOption
+#
+#   Define this function to return the text that should be put in the command line after -o to use this package.  It cannot have
+#   spaces and is not case sensitive.
+#
+#   For example, <NaturalDocs::Builder::HTML> returns 'html' so someone could use -o html [directory] to use that package.
+#
+sub CommandLineOption
+    {
+    NaturalDocs::Error->SoftDeath($_[0] . " didn't define CommandLineOption().");
+    };
+
+
+#
+#   Function: BuildFile
+#
+#   Define this function to convert a parsed file to this package's output format.  This function will be called once for every source
+#   file that needs to be rebuilt.  However, if a file hasn't changed since the last time Natural Docs was run, it will not be sent to
+#   this function.  All packages must support differential build.
+#
+#   Parameters:
+#
+#       sourceFile  - The name of the source file.
+#       parsedFile  - The parsed source file, as an arrayref of <NaturalDocs::Parser::ParsedTopic> objects.
+#
+sub BuildFile #(sourceFile, parsedFile)
+    {
+    NaturalDocs::Error->SoftDeath($_[0] . " didn't define BuildFile().");
+    };
+
+
+###############################################################################
+#
+#   Group: Optional Interface Functions
+#
+#   These functions can be implemented but packages are not required to do so.
+#
+
+
+#
+#   Function: New
+#
+#   Creates and returns a new object.
+#
+#   Note that this is the only function where the first parameter will be the package name, not the object itself.
+#
+sub New
+    {
+    my $package = shift;
+
+    my $object = [ ];
+    bless $object, $package;
+
+    return $object;
+    };
+
+
+#
+#   Function: BeginBuild
+#
+#   Define this function if the package needs to do anything at the beginning of the build process.  This function will be called
+#   every time Natural Docs is run, even if the project hasn't changed.  This allows you to manage dependencies specific
+#   to the output format that may change independently from the source tree and menu.  For example,
+#   <NaturalDocs::Builder::HTML> needs to keep the CSS files in sync regardless of whether the source tree changed or not.
+#
+#   Parameters:
+#
+#       hasChanged - Whether the project has changed, such as source files or the menu file.  If false, nothing else is going to be
+#                            called except <EndBuild()>.
+#
+sub BeginBuild #(hasChanged)
+    {
+    };
+
+
+#
+#   Function: EndBuild
+#
+#   Define this function if the package needs to do anything at the end of the build process.  This function will be called every time
+#   Natural Docs is run, even if the project hasn't changed.  This allows you to manage dependencies specific to the output
+#   format that may change independently from the source tree.  For example, <NaturalDocs::Builder::HTML> needs to keep the
+#   CSS files in sync regardless of whether the source tree changed or not.
+#
+#   Parameters:
+#
+#       hasChanged - Whether the project has changed, such as source files or the menu file.  If false, the only other function that
+#                            was called was <BeginBuild()>.
+#
+sub EndBuild #(hasChanged)
+    {
+    };
+
+
+#
+#   Function: BuildIndex
+#
+#   Define this function to create an index for the passed topic.  You can get the index from
+#   <NaturalDocs::SymbolTable->Index()>.
+#
+#   The reason it's not passed directly to this function is because indexes may be time-consuming to create.  As such, they're
+#   generated on demand because some output packages may choose not to implement them.
+#
+#   Parameters:
+#
+#       topic  - The <TopicType> to limit the index by.
+#
+sub BuildIndex #(topic)
+    {
+    };
+
+
+#
+#   Function: UpdateImage
+#
+#   Define this function to add or update the passed image in the output.
+#
+#   Parameters:
+#
+#       file - The image <FileName>
+#
+sub UpdateImage #(file)
+    {
+    };
+
+
+#
+#   Function: PurgeFiles
+#
+#   Define this function to make the package remove all output related to the passed files.  These files no longer have Natural Docs
+#   content.
+#
+#   Parameters:
+#
+#       files - An existence hashref of the files to purge.
+#
+sub PurgeFiles #(files)
+    {
+    };
+
+
+#
+#   Function: PurgeIndexes
+#
+#   Define this function to make the package remove all output related to the passed indexes.  These indexes are no longer part
+#   of the menu.
+#
+#   Parameters:
+#
+#       indexes  - An existence hashref of the <TopicTypes> of the indexes to purge.
+#
+sub PurgeIndexes #(indexes)
+    {
+    };
+
+
+#
+#   Function: PurgeImages
+#
+#   Define this function to make the package remove all output related to the passed image files.  These files are no longer used
+#   by the documentation.
+#
+#   Parameters:
+#
+#       files - An existence hashref of the image <FileNames> to purge.
+#
+sub PurgeImages #(files)
+    {
+    };
+
+
+#
+#   Function: UpdateMenu
+#
+#   Define this function to make the package update the menu.  It will only be called if the menu changed.
+#
+sub UpdateMenu
+    {
+    };
+
+
+1;

Added: trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Builder/FramedHTML.pm
===================================================================
--- trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Builder/FramedHTML.pm	                        (rev 0)
+++ trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Builder/FramedHTML.pm	2009-07-01 15:30:00 UTC (rev 4275)
@@ -0,0 +1,345 @@
+###############################################################################
+#
+#   Package: NaturalDocs::Builder::FramedHTML
+#
+###############################################################################
+#
+#   A package that generates output in HTML with frames.
+#
+#   All functions are called with Package->Function() notation.
+#
+###############################################################################
+
+# 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::Builder::FramedHTML;
+
+use base 'NaturalDocs::Builder::HTMLBase';
+
+
+###############################################################################
+# Group: Implemented Interface Functions
+
+
+#
+#   Function: INIT
+#
+#   Registers the package with <NaturalDocs::Builder>.
+#
+sub INIT
+    {
+    NaturalDocs::Builder->Add(__PACKAGE__);
+    };
+
+
+#
+#   Function: CommandLineOption
+#
+#   Returns the option to follow -o to use this package.  In this case, "html".
+#
+sub CommandLineOption
+    {
+    return 'FramedHTML';
+    };
+
+
+#
+#   Function: BuildFile
+#
+#   Builds the output file from the parsed source file.
+#
+#   Parameters:
+#
+#       sourcefile       - The <FileName> of the source file.
+#       parsedFile      - An arrayref of the source file as <NaturalDocs::Parser::ParsedTopic> objects.
+#
+sub BuildFile #(sourceFile, parsedFile)
+    {
+    my ($self, $sourceFile, $parsedFile) = @_;
+
+    my $outputFile = $self->OutputFileOf($sourceFile);
+
+
+    # 99.99% of the time the output directory will already exist, so this will actually be more efficient.  It only won't exist
+    # if a new file was added in a new subdirectory and this is the first time that file was ever parsed.
+    if (!open(OUTPUTFILEHANDLE, '>' . $outputFile))
+        {
+        NaturalDocs::File->CreatePath( NaturalDocs::File->NoFileName($outputFile) );
+
+        open(OUTPUTFILEHANDLE, '>' . $outputFile)
+            or die "Couldn't create output file " . $outputFile . "\n";
+        };
+
+    print OUTPUTFILEHANDLE
+
+
+
+        # IE 6 doesn't like any doctype here at all.  Add one (strict or transitional doesn't matter) and it makes the page slightly too
+        # wide for the frame.  Mozilla and Opera handle it like champs either way because they Don't Suck(tm).
+
+        # '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" '
+        # . '"http://www.w3.org/TR/REC-html40/loose.dtd">' . "\n\n"
+
+        '<html><head>'
+
+            . (NaturalDocs::Settings->CharSet() ?
+                '<meta http-equiv="Content-Type" content="text/html; charset=' . NaturalDocs::Settings->CharSet() . '">' : '')
+
+            . '<title>'
+                . $self->BuildTitle($sourceFile)
+            . '</title>'
+
+            . '<link rel="stylesheet" type="text/css" href="' . $self->MakeRelativeURL($outputFile, $self->MainCSSFile(), 1) . '">'
+
+            . '<script language=JavaScript src="' . $self->MakeRelativeURL($outputFile, $self->MainJavaScriptFile(), 1) . '"></script>'
+
+        . '</head><body class="FramedContentPage" onLoad="NDOnLoad()">'
+            . $self->OpeningBrowserStyles()
+
+            . $self->StandardComments()
+
+            . "\n\n\n"
+                . $self->BuildContent($sourceFile, $parsedFile)
+            . "\n\n\n"
+
+            . $self->BuildToolTips()
+
+            . $self->ClosingBrowserStyles()
+        . '</body></html>';
+
+
+    close(OUTPUTFILEHANDLE);
+    };
+
+
+#
+#   Function: BuildIndex
+#
+#   Builds an index for the passed type.
+#
+#   Parameters:
+#
+#       type  - The <TopicType> to limit the index to, or undef if none.
+#
+sub BuildIndex #(type)
+    {
+    my ($self, $type) = @_;
+
+    my $indexTitle = $self->IndexTitleOf($type);
+    my $indexFile = $self->IndexFileOf($type);
+
+    my $startIndexPage =
+
+        '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" '
+            . '"http://www.w3.org/TR/REC-html40/loose.dtd">' . "\n\n"
+
+        . '<html><head>'
+
+            . (NaturalDocs::Settings->CharSet() ?
+                '<meta http-equiv="Content-Type" content="text/html; charset=' . NaturalDocs::Settings->CharSet() . '">' : '')
+
+            . '<title>';
+
+            if (defined NaturalDocs::Menu->Title())
+                {  $startIndexPage .= $self->StringToHTML(NaturalDocs::Menu->Title()) . ' - ';  };
+
+                $startIndexPage .=
+                $indexTitle
+            . '</title>'
+
+            . '<link rel="stylesheet" type="text/css" href="' . $self->MakeRelativeURL($indexFile, $self->MainCSSFile(), 1) . '">'
+
+            . '<script language=JavaScript src="' . $self->MakeRelativeURL($indexFile, $self->MainJavaScriptFile(), 1) . '"></script>'
+
+        . '</head><body class="FramedIndexPage" onLoad="NDOnLoad()">'
+            . $self->OpeningBrowserStyles()
+
+            . "\n\n\n"
+                . $self->StandardComments()
+            . "\n\n\n"
+                . '<div id=Index>'
+                    . '<div class=IPageTitle>'
+                        . $indexTitle
+                    . '</div>';
+
+
+    my $endIndexPage =
+
+                    '</div><!--Index-->'
+                . "\n\n\n"
+
+                . $self->ClosingBrowserStyles()
+
+       . '</body></html>';
+
+    my $startSearchResultsPage =
+
+        '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" '
+            . '"http://www.w3.org/TR/REC-html40/loose.dtd">' . "\n\n"
+
+        . '<html><head>'
+
+            . (NaturalDocs::Settings->CharSet() ?
+                '<meta http-equiv="Content-Type" content="text/html; charset=' . NaturalDocs::Settings->CharSet() . '">' : '')
+
+            . '<link rel="stylesheet" type="text/css" href="' . $self->MakeRelativeURL($indexFile, $self->MainCSSFile(), 1) . '">'
+
+            . '<script language=JavaScript src="' . $self->MakeRelativeURL($indexFile, $self->MainJavaScriptFile(), 1) . '"></script>'
+            . '<script language=JavaScript src="' . $self->MakeRelativeURL($indexFile, $self->SearchDataJavaScriptFile(), 1) . '">'
+                . '</script>'
+
+        . '</head><body class="FramedSearchResultsPage" onLoad="NDOnLoad()">'
+            . $self->OpeningBrowserStyles()
+
+            . "\n\n\n"
+                . $self->StandardComments()
+            . "\n\n\n"
+
+                . '<div id=Index>'
+                    . '<div class=IPageTitle>'
+                        . 'Search Results'
+                    . '</div>';
+
+    my $endSearchResultsPage =
+
+                    '</div><!--Index-->'
+                . "\n\n\n"
+
+                . $self->ClosingBrowserStyles()
+
+       . '</body></html>';
+
+    my $indexContent = NaturalDocs::SymbolTable->Index($type);
+    my $pageCount = $self->BuildIndexPages($type, $indexContent, $startIndexPage, $endIndexPage,
+                                                                  $startSearchResultsPage, $endSearchResultsPage);
+    $self->PurgeIndexFiles($type, $indexContent, $pageCount + 1);
+    };
+
+
+#
+#   Function: UpdateMenu
+#
+#   Builds the menu file.  Also generates index.html.
+#
+sub UpdateMenu
+    {
+    my $self = shift;
+
+    my $outputDirectory = NaturalDocs::Settings->OutputDirectoryOf($self);
+    my $outputFile = NaturalDocs::File->JoinPaths($outputDirectory, 'menu.html');
+
+
+    open(OUTPUTFILEHANDLE, '>' . $outputFile)
+        or die "Couldn't create output file " . $outputFile . "\n";
+
+    my $title = 'Menu';
+    if (defined $title)
+        {  $title .= ' - ' . NaturalDocs::Menu->Title();  };
+
+    $title = $self->StringToHTML($title);
+
+
+    print OUTPUTFILEHANDLE
+
+
+        '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" '
+            . '"http://www.w3.org/TR/REC-html40/loose.dtd">' . "\n\n"
+
+        . '<html><head>'
+
+            . (NaturalDocs::Settings->CharSet() ?
+                '<meta http-equiv="Content-Type" content="text/html; charset=' . NaturalDocs::Settings->CharSet() . '">' : '')
+
+            . '<title>'
+                . $title
+            . '</title>'
+
+            . '<base target="Content">'
+
+            . '<link rel="stylesheet" type="text/css" href="' . $self->MakeRelativeURL($outputFile, $self->MainCSSFile(), 1) . '">'
+
+            . '<script language=JavaScript src="' . $self->MakeRelativeURL($outputFile, $self->MainJavaScriptFile(), 1) . '"></script>'
+            . '<script language=JavaScript src="' . $self->MakeRelativeURL($outputFile, $self->SearchDataJavaScriptFile(), 1) . '">'
+                . '</script>'
+
+        . '</head><body class="FramedMenuPage" onLoad="NDOnLoad()">'
+            . $self->OpeningBrowserStyles()
+
+            . $self->StandardComments()
+
+            . "\n\n\n"
+                . $self->BuildMenu(undef, undef)
+            . "\n\n\n"
+                . $self->BuildFooter(1)
+            . "\n\n\n"
+
+            . $self->ClosingBrowserStyles()
+        . '</body></html>';
+
+
+    close(OUTPUTFILEHANDLE);
+
+
+    # Update index.html
+
+    my $firstMenuEntry = $self->FindFirstFile();
+    my $indexFile = NaturalDocs::File->JoinPaths( NaturalDocs::Settings->OutputDirectoryOf($self), 'index.html' );
+
+    # We have to check because it's possible that there may be no files with Natural Docs content and thus no files on the menu.
+    if (defined $firstMenuEntry)
+        {
+        open(INDEXFILEHANDLE, '>' . $indexFile)
+            or die "Couldn't create output file " . $indexFile . ".\n";
+
+        print INDEXFILEHANDLE
+
+            '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Frameset//EN" '
+                . '"http://www.w3.org/TR/REC-html40/frameset.dtd">'
+
+            . '<html>'
+
+                . '<head>'
+
+                    . (NaturalDocs::Settings->CharSet() ?
+                        '<meta http-equiv="Content-Type" content="text/html; charset=' . NaturalDocs::Settings->CharSet() . '">' : '')
+
+                    . '<title>'
+                        . $self->StringToHTML(NaturalDocs::Menu->Title())
+                    . '</title>'
+
+                . '</head>'
+
+                . $self->StandardComments()
+
+                . '<frameset cols="185,*">'
+                    . '<frame name=Menu src="menu.html">'
+                    . '<frame name=Content src="'
+                        . $self->MakeRelativeURL($indexFile, $self->OutputFileOf($firstMenuEntry->Target()), 1) . '">'
+                . '</frameset>'
+
+                . '<noframes>'
+                    . 'This documentation was designed for use with frames.  However, you can still use it by '
+                    . '<a href="menu.html">starting from the menu page</a>.'
+                    . "<script language=JavaScript><!--\n"
+                        . 'location.href="menu.html";'
+                    . "\n// --></script>"
+                . '</noframes>'
+
+            . '</html>';
+
+        close INDEXFILEHANDLE;
+        }
+
+    elsif (-e $indexFile)
+        {
+        unlink($indexFile);
+        };
+    };
+
+
+1;

Added: trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Builder/HTML.pm
===================================================================
--- trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Builder/HTML.pm	                        (rev 0)
+++ trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Builder/HTML.pm	2009-07-01 15:30:00 UTC (rev 4275)
@@ -0,0 +1,398 @@
+###############################################################################
+#
+#   Package: NaturalDocs::Builder::HTML
+#
+###############################################################################
+#
+#   A package that generates output in HTML.
+#
+#   All functions are called with Package->Function() notation.
+#
+###############################################################################
+
+# 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::Builder::HTML;
+
+use base 'NaturalDocs::Builder::HTMLBase';
+
+
+###############################################################################
+# Group: Implemented Interface Functions
+
+
+#
+#   Function: INIT
+#
+#   Registers the package with <NaturalDocs::Builder>.
+#
+sub INIT
+    {
+    NaturalDocs::Builder->Add(__PACKAGE__);
+    };
+
+
+#
+#   Function: CommandLineOption
+#
+#   Returns the option to follow -o to use this package.  In this case, "html".
+#
+sub CommandLineOption
+    {
+    return 'HTML';
+    };
+
+
+#
+#   Function: BuildFile
+#
+#   Builds the output file from the parsed source file.
+#
+#   Parameters:
+#
+#       sourcefile       - The <FileName> of the source file.
+#       parsedFile      - An arrayref of the source file as <NaturalDocs::Parser::ParsedTopic> objects.
+#
+sub BuildFile #(sourceFile, parsedFile)
+    {
+    my ($self, $sourceFile, $parsedFile) = @_;
+
+    my $outputFile = $self->OutputFileOf($sourceFile);
+
+
+    # 99.99% of the time the output directory will already exist, so this will actually be more efficient.  It only won't exist
+    # if a new file was added in a new subdirectory and this is the first time that file was ever parsed.
+    if (!open(OUTPUTFILEHANDLE, '>' . $outputFile))
+        {
+        NaturalDocs::File->CreatePath( NaturalDocs::File->NoFileName($outputFile) );
+
+        open(OUTPUTFILEHANDLE, '>' . $outputFile)
+            or die "Couldn't create output file " . $outputFile . "\n";
+        };
+
+    print OUTPUTFILEHANDLE
+
+
+        '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" '
+            . '"http://www.w3.org/TR/REC-html40/strict.dtd">' . "\n\n"
+
+        . '<html><head>'
+
+            . (NaturalDocs::Settings->CharSet() ?
+                '<meta http-equiv="Content-Type" content="text/html; charset=' . NaturalDocs::Settings->CharSet() . '">' : '')
+
+            . '<title>'
+                . $self->BuildTitle($sourceFile)
+            . '</title>'
+
+            . '<link rel="stylesheet" type="text/css" href="' . $self->MakeRelativeURL($outputFile, $self->MainCSSFile(), 1) . '">'
+
+            . '<script language=JavaScript src="' . $self->MakeRelativeURL($outputFile, $self->MainJavaScriptFile(), 1) . '">'
+                . '</script>'
+            . '<script language=JavaScript src="' . $self->MakeRelativeURL($outputFile, $self->SearchDataJavaScriptFile(), 1) . '">'
+                . '</script>'
+
+        . '</head><body class="ContentPage" onLoad="NDOnLoad()">'
+            . $self->OpeningBrowserStyles()
+
+            . $self->StandardComments()
+
+            . "\n\n\n"
+                . $self->BuildContent($sourceFile, $parsedFile)
+            . "\n\n\n"
+                . $self->BuildFooter()
+            . "\n\n\n"
+                . $self->BuildMenu($sourceFile, undef)
+            . "\n\n\n"
+                . $self->BuildToolTips()
+            . "\n\n\n"
+                . '<div id=MSearchResultsWindow>'
+                    . '<iframe src="" frameborder=0 name=MSearchResults id=MSearchResults></iframe>'
+                    . '<a href="javascript:searchPanel.CloseResultsWindow()" id=MSearchResultsWindowClose>Close</a>'
+                . '</div>'
+            . "\n\n\n"
+
+            . $self->ClosingBrowserStyles()
+        . '</body></html>';
+
+
+    close(OUTPUTFILEHANDLE);
+    };
+
+
+#
+#   Function: BuildIndex
+#
+#   Builds an index for the passed type.
+#
+#   Parameters:
+#
+#       type  - The <TopicType> to limit the index to, or undef if none.
+#
+sub BuildIndex #(type)
+    {
+    my ($self, $type) = @_;
+
+    my $indexTitle = $self->IndexTitleOf($type);
+
+    my $startIndexPage =
+
+        '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" '
+            . '"http://www.w3.org/TR/REC-html40/strict.dtd">' . "\n\n"
+
+        . '<html><head>'
+
+            . (NaturalDocs::Settings->CharSet() ?
+                '<meta http-equiv="Content-Type" content="text/html; charset=' . NaturalDocs::Settings->CharSet() . '">' : '')
+
+            . '<title>'
+                . $indexTitle;
+
+                if (defined NaturalDocs::Menu->Title())
+                    {  $startIndexPage .= ' - ' . $self->StringToHTML(NaturalDocs::Menu->Title());  };
+
+            $startIndexPage .=
+            '</title>'
+
+            . '<link rel="stylesheet" type="text/css" href="' . $self->MakeRelativeURL($self->IndexDirectory(),
+                                                                                                                       $self->MainCSSFile()) . '">'
+
+            . '<script language=JavaScript src="' . $self->MakeRelativeURL($self->IndexDirectory(),
+                                                                                                        $self->MainJavaScriptFile()) . '"></script>'
+            . '<script language=JavaScript src="' . $self->MakeRelativeURL($self->IndexDirectory(),
+                                                                                                        $self->SearchDataJavaScriptFile()) . '">'
+                . '</script>'
+
+        . '</head><body class="IndexPage" onLoad="NDOnLoad()">'
+            . $self->OpeningBrowserStyles()
+
+        . $self->StandardComments()
+
+        . "\n\n\n"
+
+        . '<div id=Index>'
+            . '<div class=IPageTitle>'
+                . $indexTitle
+            . '</div>';
+
+    my $endIndexPage =
+            '</div><!--Index-->'
+
+            . "\n\n\n"
+                . $self->BuildFooter()
+            . "\n\n\n"
+                . $self->BuildMenu(undef, $type)
+            . "\n\n\n"
+                . '<div id=MSearchResultsWindow>'
+                    . '<iframe src="" frameborder=0 name=MSearchResults id=MSearchResults></iframe>'
+                    . '<a href="javascript:searchPanel.CloseResultsWindow()" id=MSearchResultsWindowClose>Close</a>'
+                . '</div>'
+            . "\n\n\n"
+
+            . $self->ClosingBrowserStyles()
+        . '</body></html>';
+
+
+    my $startSearchResultsPage =
+
+        '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" '
+            . '"http://www.w3.org/TR/REC-html40/strict.dtd">' . "\n\n"
+
+        . '<html><head>'
+
+            . (NaturalDocs::Settings->CharSet() ?
+                '<meta http-equiv="Content-Type" content="text/html; charset=' . NaturalDocs::Settings->CharSet() . '">' : '')
+
+            . '<link rel="stylesheet" type="text/css" href="' . $self->MakeRelativeURL($self->SearchResultsDirectory(),
+                                                                                                                       $self->MainCSSFile()) . '">'
+
+            . '<script language=JavaScript src="' . $self->MakeRelativeURL($self->SearchResultsDirectory(),
+                                                                                                        $self->MainJavaScriptFile()) . '"></script>'
+
+        . '</head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()">'
+            . $self->OpeningBrowserStyles()
+
+        . $self->StandardComments()
+
+        . "\n\n\n"
+
+        . '<div id=Index>';
+
+
+    my $endSearchResultsPage =
+        '</div>'
+        . $self->ClosingBrowserStyles()
+   . '</body></html>';
+
+    my $indexContent = NaturalDocs::SymbolTable->Index($type);
+    my $pageCount = $self->BuildIndexPages($type, $indexContent, $startIndexPage, $endIndexPage,
+                                                                  $startSearchResultsPage, $endSearchResultsPage);
+    $self->PurgeIndexFiles($type, $indexContent, $pageCount + 1);
+    };
+
+
+#
+#   Function: UpdateMenu
+#
+#   Updates the menu in all the output files that weren't rebuilt.  Also generates index.html.
+#
+sub UpdateMenu
+    {
+    my $self = shift;
+
+
+    # Update the menu on unbuilt files.
+
+    my $filesToUpdate = NaturalDocs::Project->UnbuiltFilesWithContent();
+
+    foreach my $sourceFile (keys %$filesToUpdate)
+        {
+        $self->UpdateFile($sourceFile);
+        };
+
+
+    # Update the menu on unchanged index files.
+
+    my $indexes = NaturalDocs::Menu->Indexes();
+
+    foreach my $index (keys %$indexes)
+        {
+        if (!NaturalDocs::SymbolTable->IndexChanged($index))
+            {
+            $self->UpdateIndex($index);
+            };
+        };
+
+
+    # Update index.html
+
+    my $firstMenuEntry = $self->FindFirstFile();
+    my $indexFile = NaturalDocs::File->JoinPaths( NaturalDocs::Settings->OutputDirectoryOf($self), 'index.html' );
+
+    # We have to check because it's possible that there may be no files with Natural Docs content and thus no files on the menu.
+    if (defined $firstMenuEntry)
+        {
+        open(INDEXFILEHANDLE, '>' . $indexFile)
+            or die "Couldn't create output file " . $indexFile . ".\n";
+
+        print INDEXFILEHANDLE
+        '<html><head>'
+             . '<meta http-equiv="Refresh" CONTENT="0; URL='
+                 . $self->MakeRelativeURL( NaturalDocs::File->JoinPaths( NaturalDocs::Settings->OutputDirectoryOf($self), 'index.html'),
+                                                        $self->OutputFileOf($firstMenuEntry->Target()), 1 ) . '">'
+        . '</head></html>';
+
+        close INDEXFILEHANDLE;
+        }
+
+    elsif (-e $indexFile)
+        {
+        unlink($indexFile);
+        };
+    };
+
+
+
+###############################################################################
+# Group: Support Functions
+
+
+#
+#   Function: UpdateFile
+#
+#   Updates an output file.  Replaces the menu, HTML title, and footer.  It opens the output file, makes the changes, and saves it
+#   back to disk, which is much quicker than rebuilding the file from scratch if these were the only things that changed.
+#
+#   Parameters:
+#
+#       sourceFile - The source <FileName>.
+#
+#   Dependencies:
+#
+#       - Requires <Builder::BuildMenu()> to surround its content with the exact strings "<div id=Menu>" and "</div><!--Menu-->".
+#       - Requires <Builder::BuildFooter()> to surround its content with the exact strings "<div id=Footer>" and
+#         "</div><!--Footer-->".
+#
+sub UpdateFile #(sourceFile)
+    {
+    my ($self, $sourceFile) = @_;
+
+    my $outputFile = $self->OutputFileOf($sourceFile);
+
+    if (open(OUTPUTFILEHANDLE, '<' . $outputFile))
+        {
+        my $content;
+
+        read(OUTPUTFILEHANDLE, $content, -s OUTPUTFILEHANDLE);
+        close(OUTPUTFILEHANDLE);
+
+
+        $content =~ s{<title>[^<]*<\/title>}{'<title>' . $self->BuildTitle($sourceFile) . '</title>'}e;
+
+        $content =~ s/<div id=Menu>.*?<\/div><!--Menu-->/$self->BuildMenu($sourceFile, undef)/es;
+
+        $content =~ s/<div id=Footer>.*?<\/div><!--Footer-->/$self->BuildFooter()/e;
+
+
+        open(OUTPUTFILEHANDLE, '>' . $outputFile);
+        print OUTPUTFILEHANDLE $content;
+        close(OUTPUTFILEHANDLE);
+        };
+    };
+
+
+#
+#   Function: UpdateIndex
+#
+#   Updates an index's output file.  Replaces the menu and footer.  It opens the output file, makes the changes, and saves it
+#   back to disk, which is much quicker than rebuilding the file from scratch if these were the only things that changed.
+#
+#   Parameters:
+#
+#       type - The index <TopicType>, or undef if none.
+#
+sub UpdateIndex #(type)
+    {
+    my ($self, $type) = @_;
+
+    my $page = 1;
+
+    my $outputFile = $self->IndexFileOf($type, $page);
+
+    my $newMenu = $self->BuildMenu(undef, $type);
+    my $newFooter = $self->BuildFooter();
+
+    while (-e $outputFile)
+        {
+        open(OUTPUTFILEHANDLE, '<' . $outputFile)
+            or die "Couldn't open output file " . $outputFile . ".\n";
+
+        my $content;
+
+        read(OUTPUTFILEHANDLE, $content, -s OUTPUTFILEHANDLE);
+        close(OUTPUTFILEHANDLE);
+
+
+        $content =~ s/<div id=Menu>.*?<\/div><!--Menu-->/$newMenu/es;
+
+        $content =~ s/<div id=Footer>.*<\/div><!--Footer-->/$newFooter/e;
+
+
+        open(OUTPUTFILEHANDLE, '>' . $outputFile)
+            or die "Couldn't save output file " . $outputFile . ".\n";
+
+        print OUTPUTFILEHANDLE $content;
+        close(OUTPUTFILEHANDLE);
+
+        $page++;
+        $outputFile = $self->IndexFileOf($type, $page);
+        };
+    };
+
+
+1;

Added: trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Builder/HTMLBase.pm
===================================================================
--- trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Builder/HTMLBase.pm	                        (rev 0)
+++ trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Builder/HTMLBase.pm	2009-07-01 15:30:00 UTC (rev 4275)
@@ -0,0 +1,3693 @@
+###############################################################################
+#
+#   Package: NaturalDocs::Builder::HTMLBase
+#
+###############################################################################
+#
+#   A base package for all the shared functionality in <NaturalDocs::Builder::HTML> and
+#   <NaturalDocs::Builder::FramedHTML>.
+#
+###############################################################################
+
+# 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::Builder::HTMLBase;
+
+use base 'NaturalDocs::Builder::Base';
+
+use NaturalDocs::DefineMembers 'MADE_EMPTY_SEARCH_RESULTS_PAGE', 'MadeEmptySearchResultsPage()',
+                                                 'SetMadeEmptySearchResultsPage()';
+
+
+
+###############################################################################
+# Group: Object Variables
+
+
+#
+#   Constants: Members
+#
+#   The object is implemented as a blessed arrayref, with the follow constants as indexes.
+#
+#   MADE_EMPTY_SEARCH_RESULTS_PAGE - Whether the search results page for searches with no results was generated.
+#
+
+#
+#   Constants: NDMarkupToHTML Styles
+#
+#   These are the styles used with <NDMarkupToHTML()>.
+#
+#   NDMARKUPTOHTML_GENERAL - General style.
+#   NDMARKUPTOHTML_SUMMARY - For summaries.
+#   NDMARKUPTOHTML_TOOLTIP - For tooltips.
+#
+use constant NDMARKUPTOHTML_GENERAL => undef;
+use constant NDMARKUPTOHTML_SUMMARY => 1;
+use constant NDMARKUPTOHTML_TOOLTIP => 2;
+
+
+
+###############################################################################
+# Group: Package Variables
+# These variables are shared by all instances of the package so don't change them.
+
+
+#
+#   handle: FH_CSS_FILE
+#
+#   The file handle to use when updating CSS files.
+#
+
+
+#
+#   Hash: abbreviations
+#
+#   An existence hash of acceptable abbreviations.  These are words that <AddDoubleSpaces()> won't put a second space
+#   after when followed by period-whitespace-capital letter.  Yes, this is seriously over-engineered.
+#
+my %abbreviations = ( mr => 1, mrs => 1, ms => 1, dr => 1,
+                                  rev => 1, fr => 1, 'i.e' => 1,
+                                  maj => 1, gen => 1, pres => 1, sen => 1, rep => 1,
+                                  n => 1, s => 1, e => 1, w => 1, ne => 1, se => 1, nw => 1, sw => 1 );
+
+#
+#   array: indexHeadings
+#
+#   An array of the headings of all the index sections.  First is for symbols, second for numbers, and the rest for each letter.
+#
+my @indexHeadings = ( '$#!', '0-9', 'A' .. 'Z' );
+
+#
+#   array: indexAnchors
+#
+#   An array of the HTML anchors of all the index sections.  First is for symbols, second for numbers, and the rest for each letter.
+#
+my @indexAnchors = ( 'Symbols', 'Numbers', 'A' .. 'Z' );
+
+#
+#   array: searchExtensions
+#
+#   An array of the search file name extensions for all the index sections.  First is for symbols, second for numbers, and the rest
+#   for each letter.
+#
+my @searchExtensions = ( 'Symbols', 'Numbers', 'A' .. 'Z' );
+
+#
+#   bool: saidUpdatingCSSFile
+#
+#   Whether the status message "Updating CSS file..." has been displayed.  We only want to print it once, no matter how many
+#   HTML-based targets we are building.
+#
+my $saidUpdatingCSSFile;
+
+#
+#   constant: ADD_HIDDEN_BREAKS
+#
+#   Just a synonym for "1" so that setting the flag on <StringToHTML()> is clearer in the calling code.
+#
+use constant ADD_HIDDEN_BREAKS => 1;
+
+
+###############################################################################
+# Group: ToolTip Package Variables
+#
+#   These variables are for the tooltip generation functions only.  Since they're reset on every call to <BuildContent()> and
+#   <BuildIndexSections()>, and are only used by them and their support functions, they can be shared by all instances of the
+#   package.
+
+#
+#   int: tooltipLinkNumber
+#
+#   A number used as part of the ID for each link that has a tooltip.  Should be incremented whenever one is made.
+#
+my $tooltipLinkNumber;
+
+#
+#   int: tooltipNumber
+#
+#   A number used as part of the ID for each tooltip.  Should be incremented whenever one is made.
+#
+my $tooltipNumber;
+
+#
+#   hash: tooltipSymbolsToNumbers
+#
+#   A hash that maps the tooltip symbols to their assigned numbers.
+#
+my %tooltipSymbolsToNumbers;
+
+#
+#   string: tooltipHTML
+#
+#   The generated tooltip HTML.
+#
+my $tooltipHTML;
+
+
+###############################################################################
+# Group: Menu Package Variables
+#
+#   These variables are for the menu generation functions only.  Since they're reset on every call to <BuildMenu()> and are
+#   only used by it and its support functions, they can be shared by all instances of the package.
+#
+
+
+#
+#   hash: prebuiltMenus
+#
+#   A hash that maps output directonies to menu HTML already built for it.  There will be no selection or JavaScript in the menus.
+#
+my %prebuiltMenus;
+
+
+#
+#   bool: menuNumbersAndLengthsDone
+#
+#   Set when the variables that only need to be calculated for the menu once are done.  This includes <menuGroupNumber>,
+#   <menuLength>, <menuGroupLengths>, and <menuGroupNumbers>, and <menuRootLength>.
+#
+my $menuNumbersAndLengthsDone;
+
+
+#
+#   int: menuGroupNumber
+#
+#   The current menu group number.  Each time a group is created, this is incremented so that each one will be unique.
+#
+my $menuGroupNumber;
+
+
+#
+#   int: menuLength
+#
+#   The length of the entire menu, fully expanded.  The value is computed from the <Menu Length Constants>.
+#
+my $menuLength;
+
+
+#
+#   hash: menuGroupLengths
+#
+#   A hash of the length of each group, *not* including any subgroup contents.  The keys are references to each groups'
+#   <NaturalDocs::Menu::Entry> object, and the values are their lengths computed from the <Menu Length Constants>.
+#
+my %menuGroupLengths;
+tie %menuGroupLengths, 'Tie::RefHash';
+
+
+#
+#   hash: menuGroupNumbers
+#
+#   A hash of the number of each group, as managed by <menuGroupNumber>.  The keys are references to each groups'
+#   <NaturalDocs::Menu::Entry> object, and the values are the number.
+#
+my %menuGroupNumbers;
+tie %menuGroupNumbers, 'Tie::RefHash';
+
+
+#
+#   int: menuRootLength
+#
+#   The length of the top-level menu entries without expansion.  The value is computed from the <Menu Length Constants>.
+#
+my $menuRootLength;
+
+
+#
+#   constants: Menu Length Constants
+#
+#   Constants used to approximate the lengths of the menu or its groups.
+#
+#   MENU_TITLE_LENGTH       - The length of the title.
+#   MENU_SUBTITLE_LENGTH - The length of the subtitle.
+#   MENU_FILE_LENGTH         - The length of one file entry.
+#   MENU_GROUP_LENGTH     - The length of one group entry.
+#   MENU_TEXT_LENGTH        - The length of one text entry.
+#   MENU_LINK_LENGTH        - The length of one link entry.
+#
+#   MENU_LENGTH_LIMIT    - The limit of the menu's length.  If the total length surpasses this limit, groups that aren't required
+#                                       to be open to show the selection will default to closed on browsers that support it.
+#
+use constant MENU_TITLE_LENGTH => 3;
+use constant MENU_SUBTITLE_LENGTH => 1;
+use constant MENU_FILE_LENGTH => 1;
+use constant MENU_GROUP_LENGTH => 2; # because it's a line and a blank space
+use constant MENU_TEXT_LENGTH => 1;
+use constant MENU_LINK_LENGTH => 1;
+use constant MENU_INDEX_LENGTH => 1;
+
+use constant MENU_LENGTH_LIMIT => 35;
+
+
+###############################################################################
+# Group: Image Package Variables
+#
+#   These variables are for the image generation functions only.  Since they're reset on every call to <BuildContent()>,
+#   and are only used by it and its support functions, they can be shared by all instances of thepackage.
+
+
+#
+#   var: imageAnchorNumber
+#   Incremented for each image link in the file that requires an anchor.
+#
+my $imageAnchorNumber;
+
+
+#
+#   var: imageContent
+#
+#   The actual embedded image HTML for all image links.  When generating an image link, the link HTML is returned and
+#   the HTML for the target image is added here.  Periodically, such as after the end of the paragraph, this content should
+#   be added to the page and the variable set to undef.
+#
+my $imageContent;
+
+
+
+###############################################################################
+# Group: Search Package Variables
+#
+#   These variables are for the search generation functions only.  Since they're reset on every call to <BuildIndexSections()> and
+#   are only used by them and their support functions, they can be shared by all instances of the package.
+
+
+#
+#   hash: searchResultIDs
+#
+#   A hash mapping lowercase-only search result IDs to the number of times they've been used.  This is to work around an IE
+#   bug where it won't correctly reference IDs if they differ only in case.
+#
+my %searchResultIDs;
+
+
+
+###############################################################################
+# Group: Object Functions
+
+
+#
+#   Function: New
+#   Creates and returns a new object.
+#
+sub New
+    {
+    my $class = shift;
+
+    my $object = $class->SUPER::New();
+    $object->SetMadeEmptySearchResultsPage(0);
+
+    return $object;
+    };
+
+
+# Function: MadeEmptySearchResultsPage
+# Returns whether the empty search results page was created or not.
+
+# Function: SetMadeEmptySearchResultsPage
+# Sets whether the empty search results page was created or not.
+
+
+
+###############################################################################
+# Group: Implemented Interface Functions
+#
+#   The behavior of these functions is shared between HTML output formats.
+#
+
+
+#
+#   Function: UpdateImage
+#
+#   Define this function to add or update the passed image in the output.
+#
+#   Parameters:
+#
+#       file - The image <FileName>
+#
+sub UpdateImage #(file)
+    {
+    my ($self, $file) = @_;
+
+    my $outputFile = $self->OutputImageOf($file);
+    my $outputDirectory = NaturalDocs::File->NoFileName($outputFile);
+
+    if (!-d $outputDirectory)
+        {  NaturalDocs::File->CreatePath($outputDirectory);  };
+
+    NaturalDocs::File->Copy($file, $outputFile);
+    };
+
+
+#
+#   Function: PurgeFiles
+#
+#   Deletes the output files associated with the purged source files.
+#
+sub PurgeFiles #(filesToPurge)
+    {
+    my ($self, $filesToPurge) = @_;
+
+    # Combine directories into a hash to remove duplicate work.
+    my %directoriesToPurge;
+
+    foreach my $file (keys %$filesToPurge)
+        {
+        # It's possible that there may be files there that aren't in a valid input directory anymore.  They won't generate an output
+        # file name so we need to check for undef.
+        my $outputFile = $self->OutputFileOf($file);
+        if (defined $outputFile)
+            {
+            unlink($outputFile);
+            $directoriesToPurge{ NaturalDocs::File->NoFileName($outputFile) } = 1;
+            };
+        };
+
+    foreach my $directory (keys %directoriesToPurge)
+        {
+        NaturalDocs::File->RemoveEmptyTree($directory, NaturalDocs::Settings->OutputDirectoryOf($self));
+        };
+    };
+
+
+#
+#   Function: PurgeIndexes
+#
+#   Deletes the output files associated with the purged source files.
+#
+#   Parameters:
+#
+#       indexes  - An existence hashref of the index types to purge.  The keys are the <TopicTypes> or * for the general index.
+#
+sub PurgeIndexes #(indexes)
+    {
+    my ($self, $indexes) = @_;
+
+    foreach my $index (keys %$indexes)
+        {
+        $self->PurgeIndexFiles($index, undef, undef);
+        };
+    };
+
+
+#
+#   Function: PurgeImages
+#
+#   Define this function to make the package remove all output related to the passed image files.  These files are no longer used
+#   by the documentation.
+#
+#   Parameters:
+#
+#       files - An existence hashref of the image <FileNames> to purge.
+#
+sub PurgeImages #(files)
+    {
+    my ($self, $filesToPurge) = @_;
+
+    # Combine directories into a hash to remove duplicate work.
+    my %directoriesToPurge;
+
+    foreach my $file (keys %$filesToPurge)
+        {
+        # It's possible that there may be files there that aren't in a valid input directory anymore.  They won't generate an output
+        # file name so we need to check for undef.
+        my $outputFile = $self->OutputImageOf($file);
+        if (defined $outputFile)
+            {
+            unlink($outputFile);
+            $directoriesToPurge{ NaturalDocs::File->NoFileName($outputFile) } = 1;
+            };
+        };
+
+    foreach my $directory (keys %directoriesToPurge)
+        {
+        NaturalDocs::File->RemoveEmptyTree($directory, NaturalDocs::Settings->OutputDirectoryOf($self));
+        };
+    };
+
+
+#
+#   Function: BeginBuild
+#
+#   Creates the necessary subdirectories in the output directory.
+#
+sub BeginBuild #(hasChanged)
+    {
+    my ($self, $hasChanged) = @_;
+
+    foreach my $directory ( $self->JavaScriptDirectory(), $self->CSSDirectory(), $self->IndexDirectory(),
+                                       $self->SearchResultsDirectory() )
+        {
+        if (!-d $directory)
+            {  NaturalDocs::File->CreatePath($directory);  };
+        };
+    };
+
+
+#
+#   Function: EndBuild
+#
+#   Synchronizes the projects CSS and JavaScript files.  Also generates the search data JavaScript file.
+#
+sub EndBuild #(hasChanged)
+    {
+    my ($self, $hasChanged) = @_;
+
+
+    # Update the style sheets.
+
+    my $styles = NaturalDocs::Settings->Styles();
+    my $changed;
+
+    my $cssDirectory = $self->CSSDirectory();
+    my $mainCSSFile = $self->MainCSSFile();
+
+    for (my $i = 0; $i < scalar @$styles; $i++)
+        {
+        my $outputCSSFile;
+
+        if (scalar @$styles == 1)
+            {  $outputCSSFile = $mainCSSFile;  }
+        else
+            {  $outputCSSFile = NaturalDocs::File->JoinPaths($cssDirectory, ($i + 1) . '.css');  };
+
+
+        my $masterCSSFile = NaturalDocs::File->JoinPaths( NaturalDocs::Settings->ProjectDirectory(), $styles->[$i] . '.css' );
+
+        if (! -e $masterCSSFile)
+            {  $masterCSSFile = NaturalDocs::File->JoinPaths( NaturalDocs::Settings->StyleDirectory(), $styles->[$i] . '.css' );  };
+
+        # We check both the date and the size in case the user switches between two styles which just happen to have the same
+        # date.  Should rarely happen, but it might.
+        if (! -e $outputCSSFile ||
+            (stat($masterCSSFile))[9] != (stat($outputCSSFile))[9] ||
+             -s $masterCSSFile != -s $outputCSSFile)
+            {
+            if (!NaturalDocs::Settings->IsQuiet() && !$saidUpdatingCSSFile)
+                {
+                print "Updating CSS file...\n";
+                $saidUpdatingCSSFile = 1;
+                };
+
+            NaturalDocs::File->Copy($masterCSSFile, $outputCSSFile);
+
+            $changed = 1;
+            };
+        };
+
+
+    my $deleteFrom;
+
+    if (scalar @$styles == 1)
+        {  $deleteFrom = 1;  }
+    else
+        {  $deleteFrom = scalar @$styles + 1;  };
+
+    for (;;)
+        {
+        my $file = NaturalDocs::File->JoinPaths($cssDirectory, $deleteFrom . '.css');
+
+        if (! -e $file)
+            {  last;  };
+
+        unlink ($file);
+        $deleteFrom++;
+
+        $changed = 1;
+        };
+
+
+    if ($changed)
+        {
+        if (scalar @$styles > 1)
+            {
+            open(FH_CSS_FILE, '>' . $mainCSSFile);
+
+            for (my $i = 0; $i < scalar @$styles; $i++)
+                {
+                print FH_CSS_FILE '@import URL("' . ($i + 1) . '.css");' . "\n";
+                };
+
+            close(FH_CSS_FILE);
+            };
+        };
+
+
+
+    # Update the JavaScript files
+
+    my $jsMaster = NaturalDocs::File->JoinPaths( NaturalDocs::Settings->JavaScriptDirectory(), 'NaturalDocs.js' );
+    my $jsOutput = $self->MainJavaScriptFile();
+
+    # We check both the date and the size in case the user switches between two styles which just happen to have the same
+    # date.  Should rarely happen, but it might.
+    if (! -e $jsOutput ||
+        (stat($jsMaster))[9] != (stat($jsOutput))[9] ||
+         -s $jsMaster != -s $jsOutput)
+        {
+        NaturalDocs::File->Copy($jsMaster, $jsOutput);
+        };
+
+
+    my @indexes = keys %{NaturalDocs::Menu->Indexes()};
+
+    open(FH_INDEXINFOJS, '>' . NaturalDocs::File->JoinPaths( $self->JavaScriptDirectory(), 'searchdata.js'));
+
+    print FH_INDEXINFOJS
+    "var indexSectionsWithContent = {\n";
+
+    for (my $i = 0; $i < scalar @indexes; $i++)
+        {
+        if ($i != 0)
+            {  print FH_INDEXINFOJS ",\n";  };
+
+        print FH_INDEXINFOJS '   "' . NaturalDocs::Topics->NameOfType($indexes[$i], 1, 1) . "\": {\n";
+
+        my $content = NaturalDocs::SymbolTable->IndexSectionsWithContent($indexes[$i]);
+        for (my $contentIndex = 0; $contentIndex < 28; $contentIndex++)
+            {
+            if ($contentIndex != 0)
+                {  print FH_INDEXINFOJS ",\n";  };
+
+            print FH_INDEXINFOJS '      "' . $searchExtensions[$contentIndex] . '": ' . ($content->[$contentIndex] ? 'true' : 'false');
+            };
+
+        print FH_INDEXINFOJS "\n      }";
+        };
+
+    print FH_INDEXINFOJS
+    "\n   }";
+
+    close(FH_INDEXINFOJS);
+    };
+
+
+
+###############################################################################
+# Group: Section Functions
+
+
+#
+#   Function: BuildTitle
+#
+#   Builds and returns the HTML page title of a file.
+#
+#   Parameters:
+#
+#       sourceFile - The source <FileName> to build the title of.
+#
+#   Returns:
+#
+#       The source file's title in HTML.
+#
+sub BuildTitle #(sourceFile)
+    {
+    my ($self, $sourceFile) = @_;
+
+    # If we have a menu title, the page title is [menu title] - [file title].  Otherwise it is just [file title].
+
+    my $title = NaturalDocs::Project->DefaultMenuTitleOf($sourceFile);
+
+    my $menuTitle = NaturalDocs::Menu->Title();
+    if (defined $menuTitle && $menuTitle ne $title)
+        {  $title .= ' - ' . $menuTitle;  };
+
+    $title = $self->StringToHTML($title);
+
+    return $title;
+    };
+
+#
+#   Function: BuildMenu
+#
+#   Builds and returns the side menu of a file.
+#
+#   Parameters:
+#
+#       sourceFile - The source <FileName> to use if you're looking for a source file.
+#       indexType - The index <TopicType> to use if you're looking for an index.
+#
+#       Both sourceFile and indexType may be undef.
+#
+#   Returns:
+#
+#       The side menu in HTML.
+#
+#   Dependencies:
+#
+#       - <Builder::HTML::UpdateFile()> and <Builder::HTML::UpdateIndex()> require this section to be surrounded with the exact
+#         strings "<div id=Menu>" and "</div><!--Menu-->".
+#       - This function depends on the way <BuildMenuSegment()> formats file and index entries.
+#
+sub BuildMenu #(FileName sourceFile, TopicType indexType) -> string htmlMenu
+    {
+    my ($self, $sourceFile, $indexType) = @_;
+
+    if (!$menuNumbersAndLengthsDone)
+        {
+        $menuGroupNumber = 1;
+        $menuLength = 0;
+        %menuGroupLengths = ( );
+        %menuGroupNumbers = ( );
+        $menuRootLength = 0;
+        };
+
+    my $outputDirectory;
+
+    if ($sourceFile)
+        {  $outputDirectory = NaturalDocs::File->NoFileName( $self->OutputFileOf($sourceFile) );  }
+    elsif ($indexType)
+        {  $outputDirectory = NaturalDocs::File->NoFileName( $self->IndexFileOf($indexType) );  }
+    else
+        {  $outputDirectory = NaturalDocs::Settings->OutputDirectoryOf($self);  };
+
+
+    # Comment needed for UpdateFile().
+    my $output = '<div id=Menu>';
+
+
+    if (!exists $prebuiltMenus{$outputDirectory})
+        {
+        my $segmentOutput;
+
+        ($segmentOutput, $menuRootLength) =
+            $self->BuildMenuSegment($outputDirectory, NaturalDocs::Menu->Content(), 1);
+
+        my $titleOutput;
+
+        my $menuTitle = NaturalDocs::Menu->Title();
+        if (defined $menuTitle)
+            {
+            if (!$menuNumbersAndLengthsDone)
+                {  $menuLength += MENU_TITLE_LENGTH;  };
+
+            $menuRootLength += MENU_TITLE_LENGTH;
+
+            $titleOutput .=
+            '<div class=MTitle>'
+                . $self->StringToHTML($menuTitle);
+
+            my $menuSubTitle = NaturalDocs::Menu->SubTitle();
+            if (defined $menuSubTitle)
+                {
+                if (!$menuNumbersAndLengthsDone)
+                    {  $menuLength += MENU_SUBTITLE_LENGTH;  };
+
+                $menuRootLength += MENU_SUBTITLE_LENGTH;
+
+                $titleOutput .=
+                '<div class=MSubTitle>'
+                    . $self->StringToHTML($menuSubTitle)
+                . '</div>';
+                };
+
+            $titleOutput .=
+            '</div>';
+            };
+
+        my $searchOutput;
+
+        if (scalar keys %{NaturalDocs::Menu->Indexes()})
+            {
+            $searchOutput =
+            '<script type="text/javascript"><!--' . "\n"
+                . 'var searchPanel = new SearchPanel("searchPanel", "' . $self->CommandLineOption() . '", '
+                    . '"' . $self->MakeRelativeURL($outputDirectory, $self->SearchResultsDirectory()) . '");' . "\n"
+            . '--></script>'
+
+            . '<div id=MSearchPanel class=MSearchPanelInactive>'
+                . '<input type=text id=MSearchField value=Search '
+                    . 'onFocus="searchPanel.OnSearchFieldFocus(true)" onBlur="searchPanel.OnSearchFieldFocus(false)" '
+                    . 'onKeyUp="searchPanel.OnSearchFieldChange()">'
+                . '<select id=MSearchType '
+                    . 'onFocus="searchPanel.OnSearchTypeFocus(true)" onBlur="searchPanel.OnSearchTypeFocus(false)" '
+                    . 'onChange="searchPanel.OnSearchTypeChange()">';
+
+                my @indexes = keys %{NaturalDocs::Menu->Indexes()};
+                @indexes = sort
+                    {
+                    if ($a eq ::TOPIC_GENERAL())  {  return -1;  }
+                    elsif ($b eq ::TOPIC_GENERAL())  {  return 1;  }
+                    else  {  return (NaturalDocs::Topics->NameOfType($a, 1) cmp NaturalDocs::Topics->NameOfType($b, 1))  };
+                    }  @indexes;
+
+                foreach my $index (@indexes)
+                    {
+                    my ($name, $extra);
+                    if ($index eq ::TOPIC_GENERAL())
+                        {
+                        $name = 'Everything';
+                        $extra = ' id=MSearchEverything selected ';
+                        }
+                    else
+                        {  $name = $self->ConvertAmpChars(NaturalDocs::Topics->NameOfType($index, 1));  }
+
+                    $searchOutput .=
+                    '<option ' . $extra . 'value="' . NaturalDocs::Topics->NameOfType($index, 1, 1) . '">'
+                        . $name
+                    . '</option>';
+                    };
+
+                $searchOutput .=
+                '</select>'
+            . '</div>';
+            };
+
+        $prebuiltMenus{$outputDirectory} = $titleOutput . $segmentOutput . $searchOutput;
+        $output .= $titleOutput . $segmentOutput . $searchOutput;
+        }
+    else
+        {  $output .= $prebuiltMenus{$outputDirectory};  };
+
+
+    # Highlight the menu selection.
+
+    if ($sourceFile)
+        {
+        # Dependency: This depends on how BuildMenuSegment() formats file entries.
+        my $outputFile = $self->OutputFileOf($sourceFile);
+        my $tag = '<div class=MFile><a href="' . $self->MakeRelativeURL($outputDirectory, $outputFile) . '">';
+        my $tagIndex = index($output, $tag);
+
+        if ($tagIndex != -1)
+            {
+            my $endIndex = index($output, '</a>', $tagIndex);
+
+            substr($output, $endIndex, 4, '');
+            substr($output, $tagIndex, length($tag), '<div class=MFile id=MSelected>');
+            };
+        }
+    elsif ($indexType)
+        {
+        # Dependency: This depends on how BuildMenuSegment() formats index entries.
+        my $outputFile = $self->IndexFileOf($indexType);
+        my $tag = '<div class=MIndex><a href="' . $self->MakeRelativeURL($outputDirectory, $outputFile) . '">';
+        my $tagIndex = index($output, $tag);
+
+        if ($tagIndex != -1)
+            {
+            my $endIndex = index($output, '</a>', $tagIndex);
+
+            substr($output, $endIndex, 4, '');
+            substr($output, $tagIndex, length($tag), '<div class=MIndex id=MSelected>');
+            };
+        };
+
+
+    # If the completely expanded menu is too long, collapse all the groups that aren't in the selection hierarchy or near the
+    # selection.  By doing this instead of having them default to closed via CSS, any browser that doesn't support changing this at
+    # runtime will keep the menu entirely open so that its still usable.
+
+    if ($menuLength > MENU_LENGTH_LIMIT())
+        {
+        my $menuSelectionHierarchy = $self->GetMenuSelectionHierarchy($sourceFile, $indexType);
+
+        my $toExpand = $self->ExpandMenu($sourceFile, $indexType, $menuSelectionHierarchy, $menuRootLength);
+
+        $output .=
+
+        '<script language=JavaScript><!--' . "\n"
+
+        . 'HideAllBut([' . join(', ', @$toExpand) . '], ' . $menuGroupNumber . ');'
+
+        . '// --></script>';
+        };
+
+    $output .= '</div><!--Menu-->';
+
+    $menuNumbersAndLengthsDone = 1;
+
+    return $output;
+    };
+
+
+#
+#   Function: BuildMenuSegment
+#
+#   A recursive function to build a segment of the menu.  *Remember to reset the <Menu Package Variables> before calling this
+#   for the first time.*
+#
+#   Parameters:
+#
+#       outputDirectory - The output directory the menu is being built for.
+#       menuSegment - An arrayref specifying the segment of the menu to build.  Either pass the menu itself or the contents
+#                               of a group.
+#       topLevel - Whether the passed segment is the top level segment or not.
+#
+#   Returns:
+#
+#       The array ( menuHTML, length ).
+#
+#       menuHTML - The menu segment in HTML.
+#       groupLength - The length of the group, *not* including the contents of any subgroups, as computed from the
+#                            <Menu Length Constants>.
+#
+#   Dependencies:
+#
+#       - <BuildMenu()> depends on the way this function formats file and index entries.
+#
+sub BuildMenuSegment #(outputDirectory, menuSegment, topLevel)
+    {
+    my ($self, $outputDirectory, $menuSegment, $topLevel) = @_;
+
+    my $output;
+    my $groupLength = 0;
+
+    foreach my $entry (@$menuSegment)
+        {
+        if ($entry->Type() == ::MENU_GROUP())
+            {
+            my ($entryOutput, $entryLength) =
+                $self->BuildMenuSegment($outputDirectory, $entry->GroupContent());
+
+            my $entryNumber;
+
+            if (!$menuNumbersAndLengthsDone)
+                {
+                $entryNumber = $menuGroupNumber;
+                $menuGroupNumber++;
+
+                $menuGroupLengths{$entry} = $entryLength;
+                $menuGroupNumbers{$entry} = $entryNumber;
+                }
+            else
+                {  $entryNumber = $menuGroupNumbers{$entry};  };
+
+            $output .=
+            '<div class=MEntry>'
+                . '<div class=MGroup>'
+
+                    . '<a href="javascript:ToggleMenu(\'MGroupContent' . $entryNumber . '\')"'
+                         . ($self->CommandLineOption() eq 'FramedHTML' ? ' target="_self"' : '') . '>'
+                        . $self->StringToHTML($entry->Title())
+                    . '</a>'
+
+                    . '<div class=MGroupContent id=MGroupContent' . $entryNumber . '>'
+                        . $entryOutput
+                    . '</div>'
+
+                . '</div>'
+            . '</div>';
+
+            $groupLength += MENU_GROUP_LENGTH;
+            }
+
+        elsif ($entry->Type() == ::MENU_FILE())
+            {
+            my $targetOutputFile = $self->OutputFileOf($entry->Target());
+
+        # Dependency: BuildMenu() depends on how this formats file entries.
+            $output .=
+            '<div class=MEntry>'
+                . '<div class=MFile>'
+                    . '<a href="' . $self->MakeRelativeURL($outputDirectory, $targetOutputFile) . '">'
+                        . $self->StringToHTML( $entry->Title(), ADD_HIDDEN_BREAKS)
+                    . '</a>'
+                . '</div>'
+            . '</div>';
+
+            $groupLength += MENU_FILE_LENGTH;
+            }
+
+        elsif ($entry->Type() == ::MENU_TEXT())
+            {
+            $output .=
+            '<div class=MEntry>'
+                . '<div class=MText>'
+                    . $self->StringToHTML( $entry->Title() )
+                . '</div>'
+            . '</div>';
+
+            $groupLength += MENU_TEXT_LENGTH;
+            }
+
+        elsif ($entry->Type() == ::MENU_LINK())
+            {
+            $output .=
+            '<div class=MEntry>'
+                . '<div class=MLink>'
+                    . '<a href="' . $entry->Target() . '"' . ($self->CommandLineOption() eq 'FramedHTML' ? ' target="_top"' : '') . '>'
+                        . $self->StringToHTML( $entry->Title() )
+                    . '</a>'
+                . '</div>'
+            . '</div>';
+
+            $groupLength += MENU_LINK_LENGTH;
+            }
+
+        elsif ($entry->Type() == ::MENU_INDEX())
+            {
+            my $indexFile = $self->IndexFileOf($entry->Target);
+
+        # Dependency: BuildMenu() depends on how this formats index entries.
+            $output .=
+            '<div class=MEntry>'
+                . '<div class=MIndex>'
+                    . '<a href="' . $self->MakeRelativeURL( $outputDirectory, $self->IndexFileOf($entry->Target()) ) . '">'
+                        . $self->StringToHTML( $entry->Title() )
+                    . '</a>'
+                . '</div>'
+            . '</div>';
+
+            $groupLength += MENU_INDEX_LENGTH;
+            };
+        };
+
+
+    if (!$menuNumbersAndLengthsDone)
+        {  $menuLength += $groupLength;  };
+
+    return ($output, $groupLength);
+    };
+
+
+#
+#   Function: BuildContent
+#
+#   Builds and returns the main page content.
+#
+#   Parameters:
+#
+#       sourceFile - The source <FileName>.
+#       parsedFile - The parsed source file as an arrayref of <NaturalDocs::Parser::ParsedTopic> objects.
+#
+#   Returns:
+#
+#       The page content in HTML.
+#
+sub BuildContent #(sourceFile, parsedFile)
+    {
+    my ($self, $sourceFile, $parsedFile) = @_;
+
+    $self->ResetToolTips();
+    $imageAnchorNumber = 1;
+    $imageContent = undef;
+
+    my $output = '<div id=Content>';
+    my $i = 0;
+
+    while ($i < scalar @$parsedFile)
+        {
+        my $anchor = $self->SymbolToHTMLSymbol($parsedFile->[$i]->Symbol());
+
+        my $scope = NaturalDocs::Topics->TypeInfo($parsedFile->[$i]->Type())->Scope();
+
+
+        # The anchors are closed, but not around the text, so the :hover CSS style won't accidentally kick in.
+
+        my $headerType;
+
+        if ($i == 0)
+            {  $headerType = 'h1';  }
+        elsif ($scope == ::SCOPE_START() || $scope == ::SCOPE_END())
+            {  $headerType = 'h2';  }
+        else
+            {  $headerType = 'h3';  };
+
+        $output .=
+
+        '<div class="C' . NaturalDocs::Topics->NameOfType($parsedFile->[$i]->Type(), 0, 1) . '">'
+            . '<div class=CTopic' . ($i == 0 ? ' id=MainTopic' : '') . '>'
+
+                . '<' . $headerType . ' class=CTitle>'
+                    . '<a name="' . $anchor . '"></a>'
+                    . $self->StringToHTML( $parsedFile->[$i]->Title(), ADD_HIDDEN_BREAKS)
+                . '</' . $headerType . '>';
+
+
+        my $hierarchy;
+        if (NaturalDocs::Topics->TypeInfo( $parsedFile->[$i]->Type() )->ClassHierarchy())
+            {
+            $hierarchy = $self->BuildClassHierarchy($sourceFile, $parsedFile->[$i]->Symbol());
+            };
+
+        my $summary;
+        if ($i == 0 || $scope == ::SCOPE_START() || $scope == ::SCOPE_END())
+            {
+            $summary .= $self->BuildSummary($sourceFile, $parsedFile, $i);
+            };
+
+        my $hasBody;
+        if (defined $hierarchy || defined $summary ||
+            defined $parsedFile->[$i]->Prototype() || defined $parsedFile->[$i]->Body())
+            {
+            $output .= '<div class=CBody>';
+            $hasBody = 1;
+            };
+
+        $output .= $hierarchy;
+
+        if (defined $parsedFile->[$i]->Prototype())
+            {
+            $output .= $self->BuildPrototype($parsedFile->[$i]->Type(), $parsedFile->[$i]->Prototype(), $sourceFile);
+            };
+
+        if (defined $parsedFile->[$i]->Body())
+            {
+            $output .= $self->NDMarkupToHTML( $sourceFile, $parsedFile->[$i]->Body(), $parsedFile->[$i]->Symbol(),
+                                                                  $parsedFile->[$i]->Package(), $parsedFile->[$i]->Type(),
+                                                                  $parsedFile->[$i]->Using() );
+            };
+
+        $output .= $summary;
+
+
+        if ($hasBody)
+            {  $output .= '</div>';  };
+
+        $output .=
+            '</div>' # CTopic
+        . '</div>' # CType
+        . "\n\n";
+
+        $i++;
+        };
+
+    $output .= '</div><!--Content-->';
+
+    return $output;
+    };
+
+
+#
+#   Function: BuildSummary
+#
+#   Builds a summary, either for the entire file or the current class/section.
+#
+#   Parameters:
+#
+#       sourceFile - The source <FileName> the summary appears in.
+#
+#       parsedFile - A reference to the parsed source file.
+#
+#       index   - The index into the parsed file to start at.  If undef or zero, it builds a summary for the entire file.  If it's the
+#                    index of a <TopicType> that starts or ends a scope, it builds a summary for that scope
+#
+#   Returns:
+#
+#       The summary in HTML.
+#
+sub BuildSummary #(sourceFile, parsedFile, index)
+    {
+    my ($self, $sourceFile, $parsedFile, $index) = @_;
+    my $completeSummary;
+
+    if (!defined $index || $index == 0)
+        {
+        $index = 0;
+        $completeSummary = 1;
+        }
+    else
+        {
+        # Skip the scope entry.
+        $index++;
+        };
+
+    if ($index + 1 >= scalar @$parsedFile)
+        {  return undef;  };
+
+
+    my $scope = NaturalDocs::Topics->TypeInfo($parsedFile->[$index]->Type())->Scope();
+
+    # Return nothing if there's only one entry.
+    if (!$completeSummary && ($scope == ::SCOPE_START() || $scope == ::SCOPE_END()) )
+        {  return undef;  };
+
+
+    my $indent = 0;
+    my $inGroup;
+
+    my $isMarked = 0;
+
+    my $output =
+    '<!--START_ND_SUMMARY-->'
+    . '<div class=Summary><div class=STitle>Summary</div>'
+
+        # Not all browsers get table padding right, so we need a div to apply the border.
+        . '<div class=SBorder>'
+        . '<table border=0 cellspacing=0 cellpadding=0 class=STable>';
+
+        while ($index < scalar @$parsedFile)
+            {
+            my $topic = $parsedFile->[$index];
+            my $scope = NaturalDocs::Topics->TypeInfo($topic->Type())->Scope();
+
+            if (!$completeSummary && ($scope == ::SCOPE_START() || $scope == ::SCOPE_END()) )
+                {  last;  };
+
+
+            # Remove modifiers as appropriate for the current entry.
+
+            if ($scope == ::SCOPE_START() || $scope == ::SCOPE_END())
+                {
+                $indent = 0;
+                $inGroup = 0;
+                $isMarked = 0;
+                }
+            elsif ($topic->Type() eq ::TOPIC_GROUP())
+                {
+                if ($inGroup)
+                    {  $indent--;  };
+
+                $inGroup = 0;
+                $isMarked = 0;
+                };
+
+
+            $output .=
+             '<tr class="S' . ($index == 0 ? 'Main' : NaturalDocs::Topics->NameOfType($topic->Type(), 0, 1))
+                . ($indent ? ' SIndent' . $indent : '') . ($isMarked ? ' SMarked' : '') .'">'
+                . '<td class=SEntry>';
+
+           # Add the entry itself.
+
+            my $toolTipProperties;
+
+            # We only want a tooltip here if there's a protoype.  Otherwise it's redundant.
+
+            if (defined $topic->Prototype())
+                {
+                my $tooltipID = $self->BuildToolTip($topic->Symbol(), $sourceFile, $topic->Type(),
+                                                                     $topic->Prototype(), $topic->Summary());
+                $toolTipProperties = $self->BuildToolTipLinkProperties($tooltipID);
+                };
+
+            $output .=
+            '<a href="#' . $self->SymbolToHTMLSymbol($parsedFile->[$index]->Symbol()) . '" ' . $toolTipProperties . '>'
+                . $self->StringToHTML( $parsedFile->[$index]->Title(), ADD_HIDDEN_BREAKS)
+            . '</a>';
+
+
+            $output .=
+            '</td><td class=SDescription>';
+
+            if (defined $parsedFile->[$index]->Body())
+                {
+                $output .= $self->NDMarkupToHTML($sourceFile, $parsedFile->[$index]->Summary(),
+                                                                     $parsedFile->[$index]->Symbol(), $parsedFile->[$index]->Package(),
+                                                                     $parsedFile->[$index]->Type(), $parsedFile->[$index]->Using(),
+                                                                     NDMARKUPTOHTML_SUMMARY);
+                };
+
+
+            $output .=
+            '</td></tr>';
+
+
+            # Prepare the modifiers for the next entry.
+
+            if ($scope == ::SCOPE_START() || $scope == ::SCOPE_END())
+                {
+                $indent = 1;
+                $inGroup = 0;
+                }
+            elsif ($topic->Type() eq ::TOPIC_GROUP())
+                {
+                if (!$inGroup)
+                    {
+                    $indent++;
+                    $inGroup = 1;
+                    };
+                };
+
+            $isMarked ^= 1;
+            $index++;
+            };
+
+        $output .=
+        '</table>'
+    . '</div>' # Body
+    . '</div>' # Summary
+    . "<!--END_ND_SUMMARY-->";
+
+    return $output;
+    };
+
+
+#
+#   Function: BuildPrototype
+#
+#   Builds and returns the prototype as HTML.
+#
+#   Parameters:
+#
+#       type - The <TopicType> the prototype is from.
+#       prototype - The prototype to format.
+#       file - The <FileName> the prototype was defined in.
+#
+#   Returns:
+#
+#       The prototype in HTML.
+#
+sub BuildPrototype #(type, prototype, file)
+    {
+    my ($self, $type, $prototype, $file) = @_;
+
+    my $language = NaturalDocs::Languages->LanguageOf($file);
+    my $prototypeObject = $language->ParsePrototype($type, $prototype);
+
+    my $output;
+
+    if ($prototypeObject->OnlyBeforeParameters())
+        {
+        $output =
+        # A blockquote to scroll it if it's too long.
+        '<blockquote>'
+            # A surrounding table as a hack to make the div form-fit.
+            . '<table border=0 cellspacing=0 cellpadding=0 class=Prototype><tr><td>'
+                . $self->ConvertAmpChars($prototypeObject->BeforeParameters())
+            . '</td></tr></table>'
+        . '</blockquote>';
+        }
+
+    else
+        {
+        my $params = $prototypeObject->Parameters();
+        my $beforeParams = $prototypeObject->BeforeParameters();
+        my $afterParams = $prototypeObject->AfterParameters();
+
+
+        # Determine what features the prototype has and its length.
+
+        my ($hasType, $hasTypePrefix, $hasNamePrefix, $hasDefaultValue, $hasDefaultValuePrefix);
+        my $maxParamLength = 0;
+
+        foreach my $param (@$params)
+            {
+            my $paramLength = length($param->Name());
+
+            if ($param->Type())
+                {
+                $hasType = 1;
+                $paramLength += length($param->Type()) + 1;
+                };
+            if ($param->TypePrefix())
+                {
+                $hasTypePrefix = 1;
+                $paramLength += length($param->TypePrefix()) + 1;
+                };
+            if ($param->NamePrefix())
+                {
+                $hasNamePrefix = 1;
+                $paramLength += length($param->NamePrefix());
+                };
+            if ($param->DefaultValue())
+                {
+                $hasDefaultValue = 1;
+
+                # The length of the default value part is either the longest word, or 1/3 the total, whichever is longer.  We do this
+                # because we don't want parameter lines wrapping to more than three lines, and there's no guarantee that the line will
+                # wrap at all.  There's a small possibility that it could still wrap to four lines with this code, but we don't need to go
+                # crazy(er) here.
+
+                my $thirdLength = length($param->DefaultValue()) / 3;
+
+                my @words = split(/ +/, $param->DefaultValue());
+                my $maxWordLength = 0;
+
+                foreach my $word (@words)
+                    {
+                    if (length($word) > $maxWordLength)
+                        {  $maxWordLength = length($word);  };
+                    };
+
+                $paramLength += ($maxWordLength > $thirdLength ? $maxWordLength : $thirdLength) + 1;
+                };
+            if ($param->DefaultValuePrefix())
+                {
+                $hasDefaultValuePrefix = 1;
+                $paramLength += length($param->DefaultValuePrefix()) + 1;
+                };
+
+            if ($paramLength > $maxParamLength)
+                {  $maxParamLength = $paramLength;  };
+            };
+
+        my $useCondensed = (length($beforeParams) + $maxParamLength + length($afterParams) > 80 ? 1 : 0);
+        my $parameterColumns = 1 + $hasType + $hasTypePrefix + $hasNamePrefix +
+                                               $hasDefaultValue + $hasDefaultValuePrefix + $useCondensed;
+
+        $output =
+        '<blockquote><table border=0 cellspacing=0 cellpadding=0 class=Prototype><tr><td>'
+
+            # Stupid hack to get it to work right in IE.
+            . '<table border=0 cellspacing=0 cellpadding=0><tr>'
+
+            . '<td class=PBeforeParameters ' . ($useCondensed ? 'colspan=' . $parameterColumns : 'nowrap') . '>'
+                . $self->ConvertAmpChars($beforeParams);
+
+                if ($beforeParams && $beforeParams !~ /[\(\[\{\<]$/)
+                    {  $output .= '&nbsp;';  };
+
+            $output .=
+            '</td>';
+
+            for (my $i = 0; $i < scalar @$params; $i++)
+                {
+                if ($useCondensed)
+                    {
+                    $output .= '</tr><tr><td>&nbsp;&nbsp;&nbsp;</td>';
+                    }
+                elsif ($i > 0)
+                    {
+                    # Go to the next row and and skip the BeforeParameters cell.
+                    $output .= '</tr><tr><td></td>';
+                    };
+
+                if ($language->TypeBeforeParameter())
+                    {
+                    if ($hasTypePrefix)
+                        {
+                        my $htmlTypePrefix = $self->ConvertAmpChars($params->[$i]->TypePrefix());
+                        $htmlTypePrefix =~ s/ $/&nbsp;/;
+
+                        $output .=
+                        '<td class=PTypePrefix nowrap>'
+                            . $htmlTypePrefix
+                        . '</td>';
+                        };
+
+                    if ($hasType)
+                        {
+                        $output .=
+                        '<td class=PType nowrap>'
+                            . $self->ConvertAmpChars($params->[$i]->Type()) . '&nbsp;'
+                        . '</td>';
+                        };
+
+                    if ($hasNamePrefix)
+                        {
+                        $output .=
+                        '<td class=PParameterPrefix nowrap>'
+                            . $self->ConvertAmpChars($params->[$i]->NamePrefix())
+                        . '</td>';
+                        };
+
+                    $output .=
+                    '<td class=PParameter nowrap' . ($useCondensed && !$hasDefaultValue ? ' width=100%' : '') . '>'
+                        . $self->ConvertAmpChars($params->[$i]->Name())
+                    . '</td>';
+                    }
+
+                else # !$language->TypeBeforeParameter()
+                    {
+                    $output .=
+                    '<td class=PParameter nowrap>'
+                        . $self->ConvertAmpChars( $params->[$i]->NamePrefix() . $params->[$i]->Name() )
+                    . '</td>';
+
+                    if ($hasType || $hasTypePrefix)
+                        {
+                        my $typePrefix = $params->[$i]->TypePrefix();
+                        if ($typePrefix)
+                            {  $typePrefix .= ' ';  };
+
+                        $output .=
+                        '<td class=PType nowrap' . ($useCondensed && !$hasDefaultValue ? ' width=100%' : '') . '>'
+                            . '&nbsp;' . $self->ConvertAmpChars( $typePrefix . $params->[$i]->Type() )
+                        . '</td>';
+                        };
+                    };
+
+                if ($hasDefaultValuePrefix)
+                    {
+                    $output .=
+                    '<td class=PDefaultValuePrefix>'
+
+                       . '&nbsp;' . $self->ConvertAmpChars( $params->[$i]->DefaultValuePrefix() ) . '&nbsp;'
+                    . '</td>';
+                    };
+
+                if ($hasDefaultValue)
+                    {
+                    $output .=
+                    '<td class=PDefaultValue width=100%>'
+                        . ($hasDefaultValuePrefix ? '' : '&nbsp;') . $self->ConvertAmpChars( $params->[$i]->DefaultValue() )
+                    . '</td>';
+                    };
+                };
+
+            if ($useCondensed)
+                {  $output .= '</tr><tr>';  };
+
+            $output .=
+            '<td class=PAfterParameters ' . ($useCondensed ? 'colspan=' . $parameterColumns : 'nowrap') . '>'
+                 . $self->ConvertAmpChars($afterParams);
+
+                if ($afterParams && $afterParams !~ /^[\)\]\}\>]/)
+                    {  $output .= '&nbsp;';  };
+
+            $output .=
+            '</td>'
+        . '</tr></table>'
+
+        # Hack.
+        . '</td></tr></table></blockquote>';
+       };
+
+    return $output;
+    };
+
+
+#
+#   Function: BuildFooter
+#
+#   Builds and returns the HTML footer for the page.
+#
+#   Parameters:
+#
+#       multiline - Whether it should be formatted on multiple lines or not.
+#
+#   Dependencies:
+#
+#       <Builder::HTML::UpdateFile()> and <Builder::HTML::UpdateIndex()> require this section to be surrounded with the exact
+#       strings "<div id=Footer>" and "</div><!--Footer-->".
+#
+sub BuildFooter #(bool multiline)
+    {
+    my ($self, $multiline) = @_;
+
+    my $footer = NaturalDocs::Menu->Footer();
+    my $timestamp = NaturalDocs::Menu->TimeStamp();
+    my $divider;
+
+    if ($multiline)
+        {  $divider = '</p><p>';  }
+    else
+        {  $divider = '&nbsp; &middot;&nbsp; ';  };
+
+
+    my $output = '<div id=Footer>';
+    if ($multiline)
+        {  $output .= '<p>';  };
+
+    if (defined $footer)
+        {
+        $footer =~ s/\(c\)/&copy;/gi;
+        $footer =~ s/\(tm\)/&trade;/gi;
+        $footer =~ s/\(r\)/&reg;/gi;
+
+        $output .= $footer . $divider;
+        };
+
+    if (defined $timestamp)
+        {
+        $output .= $timestamp . $divider;
+        };
+
+    $output .=
+    '<a href="' . NaturalDocs::Settings->AppURL() . '">'
+        . 'Generated by Natural Docs'
+    . '</a>';
+
+    if ($multiline)
+        {  $output .= '</p>';  };
+
+    $output .=
+    '</div><!--Footer-->';
+
+    return $output;
+    };
+
+
+#
+#   Function: BuildToolTip
+#
+#   Builds the HTML for a symbol's tooltip and stores it in <tooltipHTML>.
+#
+#   Parameters:
+#
+#       symbol - The target <SymbolString>.
+#       file - The <FileName> the target's defined in.
+#       type - The symbol <TopicType>.
+#       prototype - The target prototype, or undef for none.
+#       summary - The target summary, or undef for none.
+#
+#   Returns:
+#
+#       If a tooltip is necessary for the link, returns the tooltip ID.  If not, returns undef.
+#
+sub BuildToolTip #(symbol, file, type, prototype, summary)
+    {
+    my ($self, $symbol, $file, $type, $prototype, $summary) = @_;
+
+    if (defined $prototype || defined $summary)
+        {
+        my $htmlSymbol = $self->SymbolToHTMLSymbol($symbol);
+        my $number = $tooltipSymbolsToNumbers{$htmlSymbol};
+
+        if (!defined $number)
+            {
+            $number = $tooltipNumber;
+            $tooltipNumber++;
+
+            $tooltipSymbolsToNumbers{$htmlSymbol} = $number;
+
+            $tooltipHTML .=
+            '<div class=CToolTip id="tt' . $number . '">'
+                . '<div class=C' . NaturalDocs::Topics->NameOfType($type, 0, 1) . '>';
+
+            if (defined $prototype)
+                {
+                $tooltipHTML .= $self->BuildPrototype($type, $prototype, $file);
+                };
+
+            if (defined $summary)
+                {
+                # The fact that we don't have scope or using shouldn't matter because links shouldn't be included in the style anyway.
+                $summary = $self->NDMarkupToHTML($file, $summary, undef, undef, $type, undef, NDMARKUPTOHTML_TOOLTIP);
+                $tooltipHTML .= $summary;
+                };
+
+            $tooltipHTML .=
+                '</div>'
+            . '</div>';
+            };
+
+        return 'tt' . $number;
+        }
+    else
+        {  return undef;  };
+    };
+
+#
+#   Function: BuildToolTips
+#
+#   Builds and returns the tooltips for the page in HTML.
+#
+sub BuildToolTips
+    {
+    my $self = shift;
+    return "\n<!--START_ND_TOOLTIPS-->\n" . $tooltipHTML . "<!--END_ND_TOOLTIPS-->\n\n";
+    };
+
+#
+#   Function: BuildClassHierarchy
+#
+#   Builds and returns a class hierarchy diagram for the passed class, if applicable.
+#
+#   Parameters:
+#
+#       file - The source <FileName>.
+#       class - The class <SymbolString> to build the hierarchy of.
+#
+sub BuildClassHierarchy #(file, symbol)
+    {
+    my ($self, $file, $symbol) = @_;
+
+    my @parents = NaturalDocs::ClassHierarchy->ParentsOf($symbol);
+    @parents = sort { ::StringCompare($a, $b) } @parents;
+
+    my @children = NaturalDocs::ClassHierarchy->ChildrenOf($symbol);
+    @children = sort { ::StringCompare($a, $b) } @children;
+
+    if (!scalar @parents && !scalar @children)
+        {  return undef;  };
+
+    my $output =
+    '<div class=ClassHierarchy>';
+
+    if (scalar @parents)
+        {
+        $output .='<table border=0 cellspacing=0 cellpadding=0><tr><td>';
+
+        foreach my $parent (@parents)
+            {
+            $output .= $self->BuildClassHierarchyEntry($file, $parent, 'CHParent', 1);
+            };
+
+        $output .= '</td></tr></table><div class=CHIndent>';
+        };
+
+    $output .=
+    '<table border=0 cellspacing=0 cellpadding=0><tr><td>'
+        . $self->BuildClassHierarchyEntry($file, $symbol, 'CHCurrent', undef)
+    . '</td></tr></table>';
+
+    if (scalar @children)
+        {
+        $output .=
+        '<div class=CHIndent>'
+            . '<table border=0 cellspacing=0 cellpadding=0><tr><td>';
+
+        if (scalar @children <= 5)
+            {
+            for (my $i = 0; $i < scalar @children; $i++)
+                {  $output .= $self->BuildClassHierarchyEntry($file, $children[$i], 'CHChild', 1);  };
+            }
+        else
+            {
+            for (my $i = 0; $i < 4; $i++)
+                {  $output .= $self->BuildClassHierarchyEntry($file, $children[$i], 'CHChild', 1);  };
+
+           $output .= '<div class=CHChildNote><div class=CHEntry>' . (scalar @children - 4) . ' other children</div></div>';
+            };
+
+        $output .=
+        '</td></tr></table>'
+        . '</div>';
+        };
+
+    if (scalar @parents)
+        {  $output .= '</div>';  };
+
+    $output .=
+    '</div>';
+
+    return $output;
+    };
+
+
+#
+#   Function: BuildClassHierarchyEntry
+#
+#   Builds and returns a single class hierarchy entry.
+#
+#   Parameters:
+#
+#       file - The source <FileName>.
+#       symbol - The class <SymbolString> whose entry is getting built.
+#       style - The style to apply to the entry, such as <CHParent>.
+#       link - Whether to build a link for this class or not.  When building the selected class' entry, this should be false.  It will
+#               automatically handle whether the symbol is defined or not.
+#
+sub BuildClassHierarchyEntry #(file, symbol, style, link)
+    {
+    my ($self, $file, $symbol, $style, $link) = @_;
+
+    my @identifiers = NaturalDocs::SymbolString->IdentifiersOf($symbol);
+    my $name = join(NaturalDocs::Languages->LanguageOf($file)->PackageSeparator(), @identifiers);
+    $name = $self->StringToHTML($name);
+
+    my $output = '<div class=' . $style . '><div class=CHEntry>';
+
+    if ($link)
+        {
+        my $target = NaturalDocs::SymbolTable->Lookup($symbol, $file);
+
+        if (defined $target)
+            {
+            my $targetFile;
+
+            if ($target->File() ne $file)
+                {  $targetFile = $self->MakeRelativeURL( $self->OutputFileOf($file), $self->OutputFileOf($target->File()), 1 );  };
+            # else leave it undef
+
+            my $targetTooltipID = $self->BuildToolTip($symbol, $target->File(), $target->Type(),
+                                                                          $target->Prototype(), $target->Summary());
+
+            my $toolTipProperties = $self->BuildToolTipLinkProperties($targetTooltipID);
+
+            $output .= '<a href="' . $targetFile . '#' . $self->SymbolToHTMLSymbol($symbol) . '" '
+                            . 'class=L' . NaturalDocs::Topics->NameOfType($target->Type(), 0, 1) . ' ' . $toolTipProperties . '>'
+                            . $name . '</a>';
+            }
+        else
+            {  $output .= $name;  };
+        }
+    else
+        {  $output .= $name;  };
+
+    $output .= '</div></div>';
+    return $output;
+    };
+
+
+#
+#   Function: OpeningBrowserStyles
+#
+#   Returns the JavaScript that will add opening browser styles if necessary.
+#
+sub OpeningBrowserStyles
+    {
+    my $self = shift;
+
+    return
+
+    '<script language=JavaScript><!--' . "\n"
+
+        # IE 4 and 5 don't understand 'undefined', so you can't say '!= undefined'.
+        . 'if (browserType) {'
+            . 'document.write("<div class=" + browserType + ">");'
+            . 'if (browserVer) {'
+                . 'document.write("<div class=" + browserVer + ">"); }'
+            . '}'
+
+    . '// --></script>';
+    };
+
+
+#
+#   Function: ClosingBrowserStyles
+#
+#   Returns the JavaScript that will close browser styles if necessary.
+#
+sub ClosingBrowserStyles
+    {
+    my $self = shift;
+
+    return
+
+    '<script language=JavaScript><!--' . "\n"
+
+        . 'if (browserType) {'
+            . 'if (browserVer) {'
+                . 'document.write("</div>"); }'
+            . 'document.write("</div>");'
+            . '}'
+
+    . '// --></script>';
+    };
+
+
+#
+#   Function: StandardComments
+#
+#   Returns the standard HTML comments that should be included in every generated file.  This includes <IEWebMark()>, so this
+#   really is required for proper functionality.
+#
+sub StandardComments
+    {
+    my $self = shift;
+
+    return "\n\n"
+
+        . '<!--  Generated by Natural Docs, version ' . NaturalDocs::Settings->TextAppVersion() . ' -->' . "\n"
+        . '<!--  ' . NaturalDocs::Settings->AppURL() . '  -->' . "\n\n"
+        . $self->IEWebMark() . "\n\n";
+    };
+
+
+#
+#   Function: IEWebMark
+#
+#   Returns the HTML comment necessary to get around the security warnings in IE starting with Windows XP Service Pack 2.
+#
+#   With this mark, the HTML page is treated as if it were in the Internet security zone instead of the Local Machine zone.  This
+#   prevents the lockdown on scripting that causes an error message to appear with each page.
+#
+#   More Information:
+#
+#       - http://www.microsoft.com/technet/prodtechnol/winxppro/maintain/sp2brows.mspx#EHAA
+#       - http://www.phdcc.com/xpsp2.htm#markoftheweb
+#
+sub IEWebMark
+    {
+    my $self = shift;
+
+    return '<!-- saved from url=(0026)http://www.naturaldocs.org -->';
+    };
+
+
+
+###############################################################################
+# Group: Index Functions
+
+
+#
+#   Function: BuildIndexPages
+#
+#   Builds an index file or files.
+#
+#   Parameters:
+#
+#       type - The <TopicType> the index is limited to, or undef for none.
+#       indexSections  - An arrayref of sections, each section being an arrayref <NaturalDocs::SymbolTable::IndexElement>
+#                               objects.  The first section is for symbols, the second for numbers, and the rest for A through Z.
+#       beginIndexPage - All the content of the HTML page up to where the index content should appear.
+#       endIndexPage - All the content of the HTML page past where the index should appear.
+#       beginSearchResultsPage - All the content of the HTML page up to where the search results content should appear.
+#       endSearchResultsPage - All the content of the HTML page past where the search results content should appear.
+#
+#   Returns:
+#
+#       The number of pages in the index.
+#
+sub BuildIndexPages #(TopicType type, NaturalDocs::SymbolTable::IndexElement[] indexSections, string beginIndexPage, string endIndexPage, string beginSearchResultsPage, string endSearchResultsPage) => int
+    {
+    my ($self, $type, $indexSections, $beginIndexPage, $endIndexPage, $beginSearchResultsPage, $endSearchResultsPage) = @_;
+
+
+    # Build the content.
+
+    my ($indexHTMLSections, $tooltipHTMLSections, $searchResultsHTMLSections) = $self->BuildIndexSections($indexSections);
+
+
+    # Generate the search result pages.
+
+   for (my $i = 0; $i < 28; $i++)
+        {
+        if ($searchResultsHTMLSections->[$i])
+            {
+            my $searchResultsFileName = $self->SearchResultsFileOf($type, $searchExtensions[$i]);
+
+            open(INDEXFILEHANDLE, '>' . $searchResultsFileName)
+                or die "Couldn't create output file " . $searchResultsFileName . ".\n";
+
+            print INDEXFILEHANDLE
+
+            $beginSearchResultsPage
+
+            . '<div class=SRStatus id=Loading>Loading...</div>'
+
+            . '<table border=0 cellspacing=0 cellpadding=0>'
+                . $searchResultsHTMLSections->[$i]
+            . '</table>'
+
+            . '<div class=SRStatus id=Searching>Searching...</div>'
+            . '<div class=SRStatus id=NoMatches>No Matches</div>'
+
+            . '<script type="text/javascript"><!--' . "\n"
+                . 'document.getElementById("Loading").style.display="none";' . "\n"
+                . 'document.getElementById("NoMatches").style.display="none";' . "\n"
+
+                . 'var searchResults = new SearchResults("searchResults", "' . $self->CommandLineOption() . '");' . "\n"
+                . 'searchResults.Search();' . "\n"
+            . '--></script>'
+
+            . $endSearchResultsPage;
+
+            close(INDEXFILEHANDLE);
+            };
+        };
+
+    if (!$self->MadeEmptySearchResultsPage())
+        {
+        my $emptySearchResultsFileName = NaturalDocs::File->JoinPaths( $self->SearchResultsDirectory(), 'NoResults.html' );
+
+        open(INDEXFILEHANDLE, '>' . $emptySearchResultsFileName)
+            or die "Couldn't create output file " . $emptySearchResultsFileName . ".\n";
+
+        print INDEXFILEHANDLE
+
+        $beginSearchResultsPage
+        . '<div class=SRStatus id=NoMatches>No Matches</div>'
+        . $endSearchResultsPage;
+
+        close(INDEXFILEHANDLE);
+
+        $self->SetMadeEmptySearchResultsPage(1);
+        };
+
+
+    # Generate the index pages.
+
+    my $page = 1;
+    my $pageSize = 0;
+    my @pageLocations;
+
+    # The maximum page size acceptable before starting a new page.  Note that this doesn't include beginPage and endPage,
+    # because we don't want something like a large menu screwing up the calculations.
+    use constant PAGESIZE_LIMIT => 50000;
+
+
+    # File the pages.
+
+    for (my $i = 0; $i < scalar @$indexHTMLSections; $i++)
+        {
+        if (!defined $indexHTMLSections->[$i])
+            {  next;  };
+
+        $pageSize += length($indexHTMLSections->[$i]) + length($tooltipHTMLSections->[$i]);
+        $pageLocations[$i] = $page;
+
+        if ($pageSize + length($indexHTMLSections->[$i+1]) + length($tooltipHTMLSections->[$i+1]) > PAGESIZE_LIMIT)
+            {
+            $page++;
+            $pageSize = 0;
+            };
+        };
+
+
+    # Build the pages.
+
+    my $indexFileName;
+    $page = -1;
+    my $oldPage = -1;
+    my $tooltips;
+    my $firstHeading;
+
+    for (my $i = 0; $i < scalar @$indexHTMLSections; $i++)
+        {
+        if (!defined $indexHTMLSections->[$i])
+            {  next;  };
+
+        $page = $pageLocations[$i];
+
+        # Switch files if we need to.
+
+        if ($page != $oldPage)
+            {
+            if ($oldPage != -1)
+                {
+                print INDEXFILEHANDLE '</table>' . $tooltips . $endIndexPage;
+                close(INDEXFILEHANDLE);
+                $tooltips = undef;
+                };
+
+            $indexFileName = $self->IndexFileOf($type, $page);
+
+            open(INDEXFILEHANDLE, '>' . $indexFileName)
+                or die "Couldn't create output file " . $indexFileName . ".\n";
+
+            print INDEXFILEHANDLE $beginIndexPage . $self->BuildIndexNavigationBar($type, $page, \@pageLocations)
+                                              . '<table border=0 cellspacing=0 cellpadding=0>';
+
+            $oldPage = $page;
+            $firstHeading = 1;
+            };
+
+        print INDEXFILEHANDLE
+        '<tr>'
+            . '<td class=IHeading' . ($firstHeading ? ' id=IFirstHeading' : '') . '>'
+                . '<a name="' . $indexAnchors[$i] . '"></a>'
+                 . $indexHeadings[$i]
+            . '</td>'
+            . '<td></td>'
+        . '</tr>'
+
+        . $indexHTMLSections->[$i];
+
+        $firstHeading = 0;
+        $tooltips .= $tooltipHTMLSections->[$i];
+        };
+
+    if ($page != -1)
+        {
+        print INDEXFILEHANDLE '</table>' . $tooltips . $endIndexPage;
+        close(INDEXFILEHANDLE);
+        }
+
+    # Build a dummy page so there's something at least.
+    else
+        {
+        $indexFileName = $self->IndexFileOf($type, 1);
+
+        open(INDEXFILEHANDLE, '>' . $indexFileName)
+            or die "Couldn't create output file " . $indexFileName . ".\n";
+
+        print INDEXFILEHANDLE
+            $beginIndexPage
+            . $self->BuildIndexNavigationBar($type, 1, \@pageLocations)
+            . 'There are no entries in the ' . lc( NaturalDocs::Topics->NameOfType($type) ) . ' index.'
+            . $endIndexPage;
+
+        close(INDEXFILEHANDLE);
+        };
+
+
+    return $page;
+    };
+
+
+#
+#   Function: BuildIndexSections
+#
+#   Builds and returns the index and search results sections in HTML.
+#
+#   Parameters:
+#
+#       index  - An arrayref of sections, each section being an arrayref <NaturalDocs::SymbolTable::IndexElement> objects.
+#                   The first section is for symbols, the second for numbers, and the rest for A through Z.
+#
+#   Returns:
+#
+#       The arrayref ( indexSections, tooltipSections, searchResultsSections ).
+#
+#       Index 0 is the symbols, index 1 is the numbers, and each following index is A through Z.  The content of each section
+#       is its HTML, or undef if there is nothing for that section.
+#
+sub BuildIndexSections #(NaturalDocs::SymbolTable::IndexElement[] index) => ( string[], string[], string[] )
+    {
+    my ($self, $indexSections) = @_;
+
+    $self->ResetToolTips();
+    %searchResultIDs = ( );
+
+    my $contentSections = [ ];
+    my $tooltipSections = [ ];
+    my $searchResultsSections = [ ];
+
+    for (my $section = 0; $section < scalar @$indexSections; $section++)
+        {
+        if (defined $indexSections->[$section])
+            {
+            my $total = scalar @{$indexSections->[$section]};
+
+            for (my $i = 0; $i < $total; $i++)
+                {
+                my $id;
+
+                if ($i == 0)
+                    {
+                    if ($total == 1)
+                        {  $id = 'IOnlySymbolPrefix';  }
+                    else
+                        {  $id = 'IFirstSymbolPrefix';  };
+                    }
+                elsif ($i == $total - 1)
+                    {  $id = 'ILastSymbolPrefix';  };
+
+                my ($content, $searchResult) = $self->BuildIndexElement($indexSections->[$section]->[$i], $id);
+                $contentSections->[$section] .= $content;
+                $searchResultsSections->[$section] .= $searchResult;
+                };
+
+            $tooltipSections->[$section] .= $self->BuildToolTips();
+            $self->ResetToolTips(1);
+            };
+        };
+
+
+    return ( $contentSections, $tooltipSections, $searchResultsSections );
+    };
+
+
+#
+#   Function: BuildIndexElement
+#
+#   Converts a <NaturalDocs::SymbolTable::IndexElement> to HTML and returns it.  It will handle all sub-elements automatically.
+#
+#   Parameters:
+#
+#       element - The <NaturalDocs::SymbolTable::IndexElement> to build.
+#       cssID - The CSS ID to apply to the prefix.
+#
+#   Recursion-Only Parameters:
+#
+#       These parameters are used internally for recursion, and should not be set.
+#
+#       symbol - If the element is below symbol level, the <SymbolString> to use.
+#       package - If the element is below package level, the package <SymbolString> to use.
+#       hasPackage - Whether the element is below package level.  Is necessary because package may need to be undef.
+#
+#   Returns:
+#
+#       The array ( indexHTML, searchResultHTML ) which is the element in the respective HTML forms.
+#
+sub BuildIndexElement #(NaturalDocs::SymbolTable::IndexElement element, string cssID, SymbolString symbol, SymbolString package, bool hasPackage) => ( string, string )
+    {
+    my ($self, $element, $cssID, $symbol, $package, $hasPackage) = @_;
+
+
+    # If we're doing a file sub-index entry...
+
+    if ($hasPackage)
+        {
+        my ($inputDirectory, $relativePath) = NaturalDocs::Settings->SplitFromInputDirectory($element->File());
+
+        return $self->BuildIndexLink($self->StringToHTML($relativePath, ADD_HIDDEN_BREAKS), $symbol,
+                                                                                 $package, $element->File(), $element->Type(),
+                                                                                 $element->Prototype(), $element->Summary(), 'IFile');
+        }
+
+
+    # If we're doing a package sub-index entry...
+
+    elsif (defined $symbol)
+
+        {
+        my $text;
+
+        if ($element->Package())
+            {
+            $text = NaturalDocs::SymbolString->ToText($element->Package(), $element->PackageSeparator());
+            $text = $self->StringToHTML($text, ADD_HIDDEN_BREAKS);
+            }
+        else
+            {  $text = 'Global';  };
+
+        if (!$element->HasMultipleFiles())
+            {
+            return $self->BuildIndexLink($text, $symbol, $element->Package(), $element->File(), $element->Type(),
+                                                      $element->Prototype(), $element->Summary(), 'IParent');
+            }
+
+        else
+            {
+            my $indexHTML =
+            '<span class=IParent>'
+                . $text
+            . '</span>'
+            . '<div class=ISubIndex>';
+
+            my $searchResultHTML = $indexHTML;
+
+            my $fileElements = $element->File();
+            foreach my $fileElement (@$fileElements)
+                {
+                my ($i, $s) = $self->BuildIndexElement($fileElement, $cssID, $symbol, $element->Package(), 1);
+                $indexHTML .= $i;
+                $searchResultHTML .= $s;
+                };
+
+            $indexHTML .= '</div>';
+            $searchResultHTML .= '</div>';
+
+            return ($indexHTML, $searchResultHTML);
+            };
+        }
+
+
+    # If we're doing a top-level symbol entry...
+
+    else
+        {
+        my $symbolText = $self->StringToHTML($element->SortableSymbol(), ADD_HIDDEN_BREAKS);
+        my $symbolPrefix = $self->StringToHTML($element->IgnoredPrefix());
+        my $searchResultID = $self->StringToSearchResultID($element->SortableSymbol());
+
+        my $indexHTML =
+        '<tr>'
+            . '<td class=ISymbolPrefix' . ($cssID ? ' id=' . $cssID : '') . '>'
+                . ($symbolPrefix || '&nbsp;')
+            . '</td><td class=IEntry>';
+
+        my $searchResultsHTML =
+        '<div class=SRResult id=' . $searchResultID . '><div class=IEntry>';
+
+            if ($symbolPrefix)
+                {  $searchResultsHTML .= '<span class=ISymbolPrefix>' . $symbolPrefix . '</span>';  };
+
+        if (!$element->HasMultiplePackages())
+            {
+            my $packageText;
+
+            if (defined $element->Package())
+                {
+                $packageText = NaturalDocs::SymbolString->ToText($element->Package(), $element->PackageSeparator());
+                $packageText = $self->StringToHTML($packageText, ADD_HIDDEN_BREAKS);
+                };
+
+            if (!$element->HasMultipleFiles())
+                {
+                my ($i, $s) =
+                    $self->BuildIndexLink($symbolText, $element->Symbol(), $element->Package(), $element->File(),
+                                                     $element->Type(), $element->Prototype(), $element->Summary(), 'ISymbol');
+                $indexHTML .= $i;
+                $searchResultsHTML .= $s;
+
+                if (defined $packageText)
+                    {
+                    $indexHTML .=
+                    ', <span class=IParent>'
+                        . $packageText
+                    . '</span>';
+
+                    $searchResultsHTML .=
+                    ', <span class=IParent>'
+                        . $packageText
+                    . '</span>';
+                    };
+                }
+            else # hasMultipleFiles but not multiplePackages
+                {
+                $indexHTML .=
+                '<span class=ISymbol>'
+                    . $symbolText
+                . '</span>';
+
+                $searchResultsHTML .=
+                q{<a href="javascript:searchResults.Toggle('} . $searchResultID . q{')" class=ISymbol>}
+                    . $symbolText
+                . '</a>';
+
+                my $output;
+
+                if (defined $packageText)
+                    {
+                    $output .=
+                    ', <span class=IParent>'
+                        . $packageText
+                    . '</span>';
+                    };
+
+                $output .=
+                '<div class=ISubIndex>';
+
+                $indexHTML .= $output;
+                $searchResultsHTML .= $output;
+
+                my $fileElements = $element->File();
+                foreach my $fileElement (@$fileElements)
+                    {
+                    my ($i, $s) = $self->BuildIndexElement($fileElement, $cssID, $element->Symbol(), $element->Package(), 1);
+                    $indexHTML .= $i;
+                    $searchResultsHTML .= $s;
+                    };
+
+                $indexHTML .= '</div>';
+                $searchResultsHTML .= '</div>';
+                };
+            }
+
+        else # hasMultiplePackages
+            {
+            $indexHTML .=
+            '<span class=ISymbol>'
+                . $symbolText
+            . '</span>'
+            . '<div class=ISubIndex>';
+
+            $searchResultsHTML .=
+            q{<a href="javascript:searchResults.Toggle('} . $searchResultID . q{')" class=ISymbol>}
+                . $symbolText
+            . '</a>'
+            . '<div class=ISubIndex>';
+
+            my $packageElements = $element->Package();
+            foreach my $packageElement (@$packageElements)
+                {
+                my ($i, $s) = $self->BuildIndexElement($packageElement, $cssID, $element->Symbol());
+                $indexHTML .= $i;
+                $searchResultsHTML .= $s;
+                };
+
+            $indexHTML .= '</div>';
+            $searchResultsHTML .= '</div>';
+            };
+
+        $indexHTML .= '</td></tr>';
+        $searchResultsHTML .= '</div></div>';
+
+        return ($indexHTML, $searchResultsHTML);
+        };
+    };
+
+
+#
+#   Function: BuildIndexLink
+#
+#   Builds and returns the HTML associated with an index link.  The HTML will be the a href tag, the text, and the closing tag.
+#
+#   Parameters:
+#
+#       text - The text of the link *in HTML*.  Use <IndexSymbolToHTML()> if necessary.
+#       symbol - The partial <SymbolString> to link to.
+#       package - The package <SymbolString> of the symbol.
+#       file - The <FileName> the symbol is defined in.
+#       type - The <TopicType> of the symbol.
+#       prototype - The prototype of the symbol, or undef if none.
+#       summary - The summary of the symbol, or undef if none.
+#       style - The CSS style to apply to the link.
+#
+#   Returns:
+#
+#       The array ( indexHTML, searchResultHTML ) which is the link in the respective forms.
+#
+sub BuildIndexLink #(string text, SymbolString symbol, SymbolString package, FileName file, TopicType type, string prototype, string summary, string style) => ( string, string )
+    {
+    my ($self, $text, $symbol, $package, $file, $type, $prototype, $summary, $style) = @_;
+
+    $symbol = NaturalDocs::SymbolString->Join($package, $symbol);
+
+    my $targetTooltipID = $self->BuildToolTip($symbol, $file, $type, $prototype, $summary);
+    my $toolTipProperties = $self->BuildToolTipLinkProperties($targetTooltipID);
+
+    my $indexHTML = '<a href="' . $self->MakeRelativeURL( $self->IndexDirectory(), $self->OutputFileOf($file) )
+                                         . '#' . $self->SymbolToHTMLSymbol($symbol) . '" ' . $toolTipProperties . ' '
+                                . 'class=' . $style . '>' . $text . '</a>';
+    my $searchResultHTML = '<a href="' . $self->MakeRelativeURL( $self->SearchResultsDirectory(), $self->OutputFileOf($file) )
+                                         . '#' . $self->SymbolToHTMLSymbol($symbol) . '" '
+                                         . ($self->CommandLineOption eq 'HTML' ? 'target=_parent ' : '')
+                                . 'class=' . $style . '>' . $text . '</a>';
+
+    return ($indexHTML, $searchResultHTML);
+    };
+
+
+#
+#   Function: BuildIndexNavigationBar
+#
+#   Builds a navigation bar for a page of the index.
+#
+#   Parameters:
+#
+#       type - The <TopicType> of the index, or undef for general.
+#       page - The page of the index the navigation bar is for.
+#       locations - An arrayref of the locations of each section.  Index 0 is for the symbols, index 1 for the numbers, and the rest
+#                       for each letter.  The values are the page numbers where the sections are located.
+#
+sub BuildIndexNavigationBar #(type, page, locations)
+    {
+    my ($self, $type, $page, $locations) = @_;
+
+    my $output = '<div class=INavigationBar>';
+
+    for (my $i = 0; $i < scalar @indexHeadings; $i++)
+        {
+        if ($i != 0)
+            {  $output .= ' &middot; ';  };
+
+        if (defined $locations->[$i])
+            {
+            $output .= '<a href="';
+
+            if ($locations->[$i] != $page)
+                {  $output .= $self->RelativeIndexFileOf($type, $locations->[$i]);  };
+
+            $output .= '#' . $indexAnchors[$i] . '">' . $indexHeadings[$i] . '</a>';
+            }
+        else
+            {
+            $output .= $indexHeadings[$i];
+            };
+        };
+
+    $output .= '</div>';
+
+    return $output;
+    };
+
+
+
+###############################################################################
+# Group: File Functions
+
+
+#
+#   Function: PurgeIndexFiles
+#
+#   Removes all or some of the output files for an index.
+#
+#   Parameters:
+#
+#       type  - The index <TopicType>.
+#       indexSections  - An arrayref of sections, each section being an arrayref <NaturalDocs::SymbolTable::IndexElement>
+#                               objects.  The first section is for symbols, the second for numbers, and the rest for A through Z.  May be
+#                               undef.
+#       startingPage - If defined, only pages starting with this number will be removed.  Otherwise all pages will be removed.
+#
+sub PurgeIndexFiles #(TopicType type, optional NaturalDocs::SymbolTable::IndexElement[] indexSections, optional int startingPage)
+    {
+    my ($self, $type, $indexSections, $page) = @_;
+
+    # First the regular index pages.
+
+    if (!defined $page)
+        {  $page = 1;  };
+
+    for (;;)
+        {
+        my $file = $self->IndexFileOf($type, $page);
+
+        if (-e $file)
+            {
+            unlink($file);
+            $page++;
+            }
+        else
+            {
+            last;
+            };
+        };
+
+
+    # Next the search results.
+
+    for (my $i = 0; $i < 28; $i++)
+        {
+        if (!$indexSections || !$indexSections->[$i])
+            {
+            my $file = $self->SearchResultsFileOf($type, $searchExtensions[$i]);
+
+            if (-e $file)
+                {  unlink($file);  };
+            };
+        };
+    };
+
+
+#
+#   Function: OutputFileOf
+#
+#   Returns the output file name of the source file.  Will be undef if it is not a file from a valid input directory.
+#
+sub OutputFileOf #(sourceFile)
+    {
+    my ($self, $sourceFile) = @_;
+
+    my ($inputDirectory, $relativeSourceFile) = NaturalDocs::Settings->SplitFromInputDirectory($sourceFile);
+    if (!defined $inputDirectory)
+        {  return undef;  };
+
+    my $outputDirectory = NaturalDocs::Settings->OutputDirectoryOf($self);
+    my $inputDirectoryName = NaturalDocs::Settings->InputDirectoryNameOf($inputDirectory);
+
+    $outputDirectory = NaturalDocs::File->JoinPaths( $outputDirectory,
+                                                                            'files' . ($inputDirectoryName != 1 ? $inputDirectoryName : ''), 1 );
+
+    # We need to change any extensions to dashes because Apache will think file.pl.html is a script.
+    # We also need to add a dash if the file doesn't have an extension so there'd be no conflicts with index.html,
+    # FunctionIndex.html, etc.
+
+    if (!($relativeSourceFile =~ tr/./-/))
+        {  $relativeSourceFile .= '-';  };
+
+    $relativeSourceFile =~ tr/ &?(){};/_/;
+    $relativeSourceFile .= '.html';
+
+    return NaturalDocs::File->JoinPaths($outputDirectory, $relativeSourceFile);
+    };
+
+
+#
+#   Function: OutputImageOf
+#
+#   Returns the output image file name of the source image file.  Will be undef if it is not a file from a valid input directory.
+#
+sub OutputImageOf #(sourceImageFile)
+    {
+    my ($self, $sourceImageFile) = @_;
+
+    my $outputDirectory = NaturalDocs::Settings->OutputDirectoryOf($self);
+    my $topLevelDirectory;
+
+    my ($inputDirectory, $relativeImageFile) = NaturalDocs::Settings->SplitFromInputDirectory($sourceImageFile);
+
+    if (defined $inputDirectory)
+        {
+        my $inputDirectoryName = NaturalDocs::Settings->InputDirectoryNameOf($inputDirectory);
+        $topLevelDirectory = 'files' . ($inputDirectoryName != 1 ? $inputDirectoryName : '');
+        }
+    else
+        {
+        ($inputDirectory, $relativeImageFile) = NaturalDocs::Settings->SplitFromImageDirectory($sourceImageFile);
+
+        if (!defined $inputDirectory)
+            {  return undef;  };
+
+        my $inputDirectoryName = NaturalDocs::Settings->ImageDirectoryNameOf($inputDirectory);
+        $topLevelDirectory = 'images' . ($inputDirectoryName != 1 ? $inputDirectoryName : '');
+        }
+
+
+    $outputDirectory = NaturalDocs::File->JoinPaths($outputDirectory, $topLevelDirectory, 1);
+
+    $relativeImageFile =~ tr/ /_/;
+
+    return NaturalDocs::File->JoinPaths($outputDirectory, $relativeImageFile);
+    };
+
+
+#
+#   Function: IndexDirectory
+#
+#   Returns the directory of the index files.
+#
+sub IndexDirectory
+    {
+    my $self = shift;
+    return NaturalDocs::File->JoinPaths( NaturalDocs::Settings->OutputDirectoryOf($self), 'index', 1);
+    };
+
+
+#
+#   Function: IndexFileOf
+#
+#   Returns the output file name of the index file.
+#
+#   Parameters:
+#
+#       type  - The <TopicType> of the index.
+#       page  - The page number.  Undef is the same as one.
+#
+sub IndexFileOf #(type, page)
+    {
+    my ($self, $type, $page) = @_;
+    return NaturalDocs::File->JoinPaths( $self->IndexDirectory(), $self->RelativeIndexFileOf($type, $page) );
+    };
+
+
+#
+#   Function: RelativeIndexFileOf
+#
+#   Returns the output file name of the index file, relative to other index files.
+#
+#   Parameters:
+#
+#       type  - The <TopicType> of the index.
+#       page  - The page number.  Undef is the same as one.
+#
+sub RelativeIndexFileOf #(type, page)
+    {
+    my ($self, $type, $page) = @_;
+    return NaturalDocs::Topics->NameOfType($type, 1, 1) . (defined $page && $page != 1 ? $page : '') . '.html';
+    };
+
+
+#
+#   Function: SearchResultsDirectory
+#
+#   Returns the directory of the search results files.
+#
+sub SearchResultsDirectory
+    {
+    my $self = shift;
+    return NaturalDocs::File->JoinPaths( NaturalDocs::Settings->OutputDirectoryOf($self), 'search', 1);
+    };
+
+
+#
+#   Function: SearchResultsFileOf
+#
+#   Returns the output file name of the search result file.
+#
+#   Parameters:
+#
+#       type  - The <TopicType> of the index.
+#       extra - The string to add to the end of the file name, such as "A" or "Symbols".
+#
+sub SearchResultsFileOf #(TopicType type, string extra)
+    {
+    my ($self, $type, $extra) = @_;
+
+    my $fileName = NaturalDocs::Topics->NameOfType($type, 1, 1) . $extra . '.html';
+
+    return NaturalDocs::File->JoinPaths( $self->SearchResultsDirectory(), $fileName );
+    };
+
+
+#
+#   Function: CSSDirectory
+#
+#   Returns the directory of the CSS files.
+#
+sub CSSDirectory
+    {
+    my $self = shift;
+    return NaturalDocs::File->JoinPaths( NaturalDocs::Settings->OutputDirectoryOf($self), 'styles', 1);
+    };
+
+
+#
+#   Function: MainCSSFile
+#
+#   Returns the location of the main CSS file.
+#
+sub MainCSSFile
+    {
+    my $self = shift;
+    return NaturalDocs::File->JoinPaths( $self->CSSDirectory(), 'main.css' );
+    };
+
+
+#
+#   Function: JavaScriptDirectory
+#
+#   Returns the directory of the JavaScript files.
+#
+sub JavaScriptDirectory
+    {
+    my $self = shift;
+    return NaturalDocs::File->JoinPaths( NaturalDocs::Settings->OutputDirectoryOf($self), 'javascript', 1);
+    };
+
+
+#
+#   Function: MainJavaScriptFile
+#
+#   Returns the location of the main JavaScript file.
+#
+sub MainJavaScriptFile
+    {
+    my $self = shift;
+    return NaturalDocs::File->JoinPaths( $self->JavaScriptDirectory(), 'main.js' );
+    };
+
+
+#
+#   Function: SearchDataJavaScriptFile
+#
+#   Returns the location of the search data JavaScript file.
+#
+sub SearchDataJavaScriptFile
+    {
+    my $self = shift;
+    return NaturalDocs::File->JoinPaths( $self->JavaScriptDirectory(), 'searchdata.js' );
+    };
+
+
+
+###############################################################################
+# Group: Support Functions
+
+
+#
+#   Function: IndexTitleOf
+#
+#   Returns the page title of the index file.
+#
+#   Parameters:
+#
+#       type  - The type of index.
+#
+sub IndexTitleOf #(type)
+    {
+    my ($self, $type) = @_;
+
+    return ($type eq ::TOPIC_GENERAL() ? '' : NaturalDocs::Topics->NameOfType($type) . ' ') . 'Index';
+    };
+
+#
+#   Function: MakeRelativeURL
+#
+#   Returns a relative path between two files in the output tree and returns it in URL format.
+#
+#   Parameters:
+#
+#       baseFile    - The base <FileName> in local format, *not* in URL format.
+#       targetFile  - The target <FileName> of the link in local format, *not* in URL format.
+#       baseHasFileName - Whether baseFile has a file name attached or is just a path.
+#
+#   Returns:
+#
+#       The relative URL to the target.
+#
+sub MakeRelativeURL #(FileName baseFile, FileName targetFile, bool baseHasFileName) -> string relativeURL
+    {
+    my ($self, $baseFile, $targetFile, $baseHasFileName) = @_;
+
+    if ($baseHasFileName)
+        {  $baseFile = NaturalDocs::File->NoFileName($baseFile)  };
+
+    my $relativePath = NaturalDocs::File->MakeRelativePath($baseFile, $targetFile);
+
+    return $self->ConvertAmpChars( NaturalDocs::File->ConvertToURL($relativePath) );
+    };
+
+#
+#   Function: StringToHTML
+#
+#   Converts a text string to HTML.  Does not apply paragraph tags or accept formatting tags.
+#
+#   Parameters:
+#
+#       string - The string to convert.
+#       addHiddenBreaks - Whether to add hidden breaks to the string.  You can use <ADD_HIDDEN_BREAKS> for this parameter
+#                                   if you want to make the calling code clearer.
+#
+#   Returns:
+#
+#       The string in HTML.
+#
+sub StringToHTML #(string, addHiddenBreaks)
+    {
+    my ($self, $string, $addHiddenBreaks) = @_;
+
+    $string =~ s/&/&amp;/g;
+    $string =~ s/</&lt;/g;
+    $string =~ s/>/&gt;/g;
+
+    # Me likey the fancy quotes.  They work in IE 4+, Mozilla, and Opera 5+.  We've already abandoned NS4 with the CSS
+    # styles, so might as well.
+    $string =~ s/^\'/&lsquo;/gm;
+    $string =~ s/([\ \(\[\{])\'/$1&lsquo;/g;
+    $string =~ s/\'/&rsquo;/g;
+
+    $string =~ s/^\"/&ldquo;/gm;
+    $string =~ s/([\ \(\[\{])\"/$1&ldquo;/g;
+    $string =~ s/\"/&rdquo;/g;
+
+    # Me likey the double spaces too.  As you can probably tell, I like print-formatting better than web-formatting.  The indented
+    # paragraphs without blank lines in between them do become readable when you have fancy quotes and double spaces too.
+    $string = $self->AddDoubleSpaces($string);
+
+    if ($addHiddenBreaks)
+        {  $string = $self->AddHiddenBreaks($string);  };
+
+    return $string;
+    };
+
+
+#
+#   Function: SymbolToHTMLSymbol
+#
+#   Converts a <SymbolString> to a HTML symbol, meaning one that is safe to include in anchor and link tags.  You don't need
+#   to pass the result to <ConvertAmpChars()>.
+#
+sub SymbolToHTMLSymbol #(symbol)
+    {
+    my ($self, $symbol) = @_;
+
+    my @identifiers = NaturalDocs::SymbolString->IdentifiersOf($symbol);
+    my $htmlSymbol = join('.', @identifiers);
+
+    # If only Mozilla was nice about putting special characters in URLs like IE and Opera are, I could leave spaces in and replace
+    # "<>& with their amp chars.  But alas, Mozilla shows them as %20, etc. instead.  It would have made for nice looking URLs.
+    $htmlSymbol =~ tr/ \"<>\?&%/_/d;
+
+    return $htmlSymbol;
+    };
+
+
+#
+#   Function: StringToSearchResultID
+#
+#   Takes a text string and translates it into something that can be used as a CSS ID.
+#
+#   Parameters:
+#
+#       string - The string to convert
+#       dontIncrement - If set, it reuses the last generated ID.  Otherwise it generates a new one if it matches a previously
+#                               generated one in a case-insensitive way.
+#
+sub StringToSearchResultID #(string string, bool dontIncrement = 0) => string
+    {
+    my ($self, $string, $dontIncrement) = @_;
+
+    $string =~ s/\_/_und/g;
+    $string =~ s/ +/_spc/g;
+
+    my %translation = ( '~' => '_til', '!' => '_exc', '@' => '_att', '#' => '_num', '$' => '_dol', '%' => '_pct', '^' => '_car',
+                                  '&' => '_amp', '*' => '_ast', '(' => '_lpa', ')' => '_rpa', '-' => '_min', '+' => '_plu', '=' => '_equ',
+                                  '{' => '_lbc', '}' => '_rbc', '[' => '_lbk', ']' => '_rbk', ':' => '_col', ';' => '_sco', '"' => '_quo',
+                                  '\'' => '_apo', '<' => '_lan', '>' => '_ran', ',' => '_com', '.' => '_per', '?' => '_que', '/' => '_sla' );
+
+    $string =~ s/([\~\!\@\#\$\%\^\&\*\(\)\-\+\=\{\}\[\]\:\;\"\'\<\>\,\.\?\/])/$translation{$1}/ge;
+    $string =~ s/[^a-z0-9_]/_zzz/gi;
+
+    my $number = $searchResultIDs{lc($string)};
+
+    if (!$number)
+        {  $number = 1;  }
+    elsif (!$dontIncrement)
+        {  $number++;  };
+
+    $searchResultIDs{lc($string)} = $number;
+
+    return 'SR' . ($number == 1 ? '' : $number) . '_' . $string;
+    };
+
+
+#
+#   Function: NDMarkupToHTML
+#
+#   Converts a block of <NDMarkup> to HTML.
+#
+#   Parameters:
+#
+#       sourceFile - The source <FileName> the <NDMarkup> appears in.
+#       text    - The <NDMarkup> text to convert.
+#       symbol - The topic <SymbolString> the <NDMarkup> appears in.
+#       package  - The package <SymbolString> the <NDMarkup> appears in.
+#       type - The <TopicType> the <NDMarkup> appears in.
+#       using - An arrayref of scope <SymbolStrings> the <NDMarkup> also has access to, or undef if none.
+#       style - Set to one of the <NDMarkupToHTML Styles> or leave undef for general.
+#
+#   Returns:
+#
+#       The text in HTML.
+#
+sub NDMarkupToHTML #(sourceFile, text, symbol, package, type, using, style)
+    {
+    my ($self, $sourceFile, $text, $symbol, $package, $type, $using, $style) = @_;
+
+    my $dlSymbolBehavior;
+
+    if ($type == ::TOPIC_ENUMERATION())
+        {  $dlSymbolBehavior = NaturalDocs::Languages->LanguageOf($sourceFile)->EnumValues();  }
+    elsif (NaturalDocs::Topics->TypeInfo($type)->Scope() == ::SCOPE_ALWAYS_GLOBAL())
+        {  $dlSymbolBehavior = ::ENUM_GLOBAL();  }
+    else
+        {  $dlSymbolBehavior = ::ENUM_UNDER_PARENT();  };
+
+    my $output;
+    my $inCode;
+
+    my @splitText = split(/(<\/?code>)/, $text);
+
+    while (scalar @splitText)
+        {
+        $text = shift @splitText;
+
+        if ($text eq '<code>')
+            {
+            $output .= '<blockquote><pre>';
+            $inCode = 1;
+            }
+        elsif ($text eq '</code>')
+            {
+            $output .= '</pre></blockquote>';
+            $inCode = undef;
+            }
+        elsif ($inCode)
+            {
+            # Leave line breaks in.
+            $output .= $text;
+            }
+        else
+            {
+            # Format non-code text.
+
+            # Convert linked images.
+            if ($text =~ /<img mode=\"link\"/)
+                {
+                if ($style == NDMARKUPTOHTML_GENERAL)
+                    {
+                    # Split by tags we would want to see the linked images appear after.  For example, an image link appearing in
+                    # the middle of a paragraph would appear after the end of that paragraph.
+                    my @imageBlocks = split(/(<p>.*?<\/p>|<dl>.*?<\/dl>|<ul>.*?<\/ul>)/, $text);
+                    $text = undef;
+
+                    foreach my $imageBlock (@imageBlocks)
+                        {
+                        $imageBlock =~ s{<img mode=\"link\" target=\"([^\"]*)\" original=\"([^\"]*)\">}
+                                                {$self->BuildImage($sourceFile, 'link', $1, $2)}ge;
+
+                        $text .= $imageBlock . $imageContent;
+                        $imageContent = undef;
+                        };
+                    }
+
+                # Use only the text for tooltips and summaries.
+                else
+                    {
+                    $text =~ s{<img mode=\"link\" target=\"[^\"]*\" original=\"([^\"]*)\">}{$1}g;
+                    };
+                };
+
+            # Convert quotes to fancy quotes.  This has to be done before links because some of them may have JavaScript
+            # attributes that use the apostrophe character.
+            $text =~ s/^\'/&lsquo;/gm;
+            $text =~ s/([\ \(\[\{])\'/$1&lsquo;/g;
+            $text =~ s/\'/&rsquo;/g;
+
+            $text =~ s/^&quot;/&ldquo;/gm;
+            $text =~ s/([\ \(\[\{])&quot;/$1&ldquo;/g;
+            $text =~ s/&quot;/&rdquo;/g;
+
+            # Resolve and convert links, except for tooltips.
+            if ($style != NDMARKUPTOHTML_TOOLTIP)
+                {
+                $text =~ s{<link target=\"([^\"]*)\" name=\"([^\"]*)\" original=\"([^\"]*)\">}
+                               {$self->BuildTextLink($1, $2, $3, $package, $using, $sourceFile)}ge;
+                $text =~ s/<url target=\"([^\"]*)\" name=\"([^\"]*)\">/$self->BuildURLLink($1, $2)/ge;
+                }
+            else
+                {
+                $text =~ s{<link target=\"[^\"]*\" name=\"([^\"]*)\" original=\"[^\"]*\">}{$1}g;
+                $text =~ s{<url target=\"[^\"]*\" name=\"([^\"]*)\">}{$1}g;
+                };
+
+            # We do full e-mail links anyway just so the obfuscation remains.
+            $text =~ s/<email target=\"([^\"]*)\" name=\"([^\"]*)\">/$self->BuildEMailLink($1, $2)/ge;
+
+
+            # Convert inline images, but only for the general style.
+            if ($style == NDMARKUPTOHTML_GENERAL)
+                {
+                $text =~ s{<img mode=\"inline\" target=\"([^\"]*)\" original=\"([^\"]*)\">}
+                               {$self->BuildImage($sourceFile, 'inline', $1, $2)}ge;
+                }
+            else
+                {
+                $text =~ s{<img mode=\"inline\" target=\"[^\"]*\" original=\"([^\"]*)\">}{$1}g;
+                };
+
+            # Copyright symbols.  Prevent conversion when part of (a), (b), (c) lists.
+            if ($text !~ /\(a\)/i)
+                {  $text =~ s/\(c\)/&copy;/gi;  };
+
+            # Trademark symbols.
+            $text =~ s/\(tm\)/&trade;/gi;
+            $text =~ s/\(r\)/&reg;/gi;
+
+            # Add double spaces too.
+            $text = $self->AddDoubleSpaces($text);
+
+            # Headings
+            $text =~ s/<h>/<h4 class=CHeading>/g;
+            $text =~ s/<\/h>/<\/h4>/g;
+
+            # Description Lists
+            $text =~ s/<dl>/<table border=0 cellspacing=0 cellpadding=0 class=CDescriptionList>/g;
+            $text =~ s/<\/dl>/<\/table>/g;
+
+            $text =~ s/<de>/<tr><td class=CDLEntry>/g;
+            $text =~ s/<\/de>/<\/td>/g;
+
+            if ($dlSymbolBehavior == ::ENUM_GLOBAL())
+                {  $text =~ s/<ds>([^<]+)<\/ds>/$self->MakeDescriptionListSymbol(undef, $1)/ge;  }
+            elsif ($dlSymbolBehavior == ::ENUM_UNDER_PARENT())
+                {  $text =~ s/<ds>([^<]+)<\/ds>/$self->MakeDescriptionListSymbol($package, $1)/ge;  }
+            else # ($dlSymbolBehavior == ::ENUM_UNDER_TYPE())
+                {  $text =~ s/<ds>([^<]+)<\/ds>/$self->MakeDescriptionListSymbol($symbol, $1)/ge;  }
+
+            sub MakeDescriptionListSymbol #(package, text)
+                {
+                my ($self, $package, $text) = @_;
+
+                $text = NaturalDocs::NDMarkup->RestoreAmpChars($text);
+                my $symbol = NaturalDocs::SymbolString->FromText($text);
+
+                if (defined $package)
+                    {  $symbol = NaturalDocs::SymbolString->Join($package, $symbol);  };
+
+                return
+                '<tr>'
+                    . '<td class=CDLEntry>'
+                        # The anchors are closed, but not around the text, to prevent the :hover CSS style from kicking in.
+                        . '<a name="' . $self->SymbolToHTMLSymbol($symbol) . '"></a>'
+                        . $text
+                    . '</td>';
+                };
+
+            $text =~ s/<dd>/<td class=CDLDescription>/g;
+            $text =~ s/<\/dd>/<\/td><\/tr>/g;
+
+            $output .= $text;
+            };
+        };
+
+    return $output;
+    };
+
+
+#
+#   Function: BuildTextLink
+#
+#   Creates a HTML link to a symbol, if it exists.
+#
+#   Parameters:
+#
+#       target  - The link text.
+#       name - The link name.
+#       original - The original text as it appears in the source.
+#       package  - The package <SymbolString> the link appears in, or undef if none.
+#       using - An arrayref of additional scope <SymbolStrings> the link has access to, or undef if none.
+#       sourceFile  - The <FileName> the link appears in.
+#
+#       Target, name, and original are assumed to still have <NDMarkup> amp chars.
+#
+#   Returns:
+#
+#       The link in HTML, including tags.  If the link doesn't resolve to anything, returns the HTML that should be substituted for it.
+#
+sub BuildTextLink #(target, name, original, package, using, sourceFile)
+    {
+    my ($self, $target, $name, $original, $package, $using, $sourceFile) = @_;
+
+    my $plainTarget = $self->RestoreAmpChars($target);
+
+    my $symbol = NaturalDocs::SymbolString->FromText($plainTarget);
+    my $symbolTarget = NaturalDocs::SymbolTable->References(::REFERENCE_TEXT(), $symbol, $package, $using, $sourceFile);
+
+    if (defined $symbolTarget)
+        {
+        my $symbolTargetFile;
+
+        if ($symbolTarget->File() ne $sourceFile)
+            {
+            $symbolTargetFile = $self->MakeRelativeURL( $self->OutputFileOf($sourceFile),
+                                                                               $self->OutputFileOf($symbolTarget->File()), 1 );
+            };
+        # else leave it undef
+
+        my $symbolTargetTooltipID = $self->BuildToolTip($symbolTarget->Symbol(), $sourceFile, $symbolTarget->Type(),
+                                                                                 $symbolTarget->Prototype(), $symbolTarget->Summary());
+
+        my $toolTipProperties = $self->BuildToolTipLinkProperties($symbolTargetTooltipID);
+
+        return '<a href="' . $symbolTargetFile . '#' . $self->SymbolToHTMLSymbol($symbolTarget->Symbol()) . '" '
+                    . 'class=L' . NaturalDocs::Topics->NameOfType($symbolTarget->Type(), 0, 1) . ' ' . $toolTipProperties . '>'
+                        . $name
+                    . '</a>';
+        }
+    else
+        {
+        return $original;
+        };
+    };
+
+
+#
+#   Function: BuildURLLink
+#
+#   Creates a HTML link to an external URL.  Long URLs will have hidden breaks to allow them to wrap.
+#
+#   Parameters:
+#
+#       target - The URL to link to.
+#       name - The label of the link.
+#
+#       Both are assumed to still have <NDMarkup> amp chars.
+#
+#   Returns:
+#
+#       The HTML link, complete with tags.
+#
+sub BuildURLLink #(target, name)
+    {
+    my ($self, $target, $name) = @_;
+
+    # Don't restore amp chars on the target.
+
+    if (length $name < 50 || $name ne $target)
+        {  return '<a href="' . $target . '" class=LURL target=_top>' . $name . '</a>';  };
+
+    my @segments = split(/([\,\/]|&amp;)/, $target);
+    my $output = '<a href="' . $target . '" class=LURL target=_top>';
+
+    # Get past the first batch of slashes, since we don't want to break on things like http://.
+
+    $output .= $segments[0];
+
+    my $i = 1;
+    while ($i < scalar @segments && ($segments[$i] eq '/' || !$segments[$i]))
+        {
+        $output .= $segments[$i];
+        $i++;
+        };
+
+    # Now break on each one of those symbols.
+
+    while ($i < scalar @segments)
+        {
+        if ($segments[$i] eq ',' || $segments[$i] eq '/' || $segments[$i] eq '&amp;')
+            {  $output .= '<wbr>';  };
+
+        $output .= $segments[$i];
+        $i++;
+        };
+
+    $output .= '</a>';
+    return $output;
+    };
+
+
+#
+#   Function: BuildEMailLink
+#
+#   Creates a HTML link to an e-mail address.  The address will be transparently munged to protect it (hopefully) from spambots.
+#
+#   Parameters:
+#
+#       target  - The e-mail address.
+#       name - The label of the link.
+#
+#       Both are assumed to still have <NDMarkup> amp chars.
+#
+#   Returns:
+#
+#       The HTML e-mail link, complete with tags.
+#
+sub BuildEMailLink #(target, name)
+    {
+    my ($self, $target, $name) = @_;
+    my @splitAddress;
+
+
+    # Hack the address up.  We want two user pieces and two host pieces.
+
+    my ($user, $host) = split(/\@/, $self->RestoreAmpChars($target));
+
+    my $userSplit = length($user) / 2;
+
+    push @splitAddress, NaturalDocs::NDMarkup->ConvertAmpChars( substr($user, 0, $userSplit) );
+    push @splitAddress, NaturalDocs::NDMarkup->ConvertAmpChars( substr($user, $userSplit) );
+
+    push @splitAddress, '@';
+
+    my $hostSplit = length($host) / 2;
+
+    push @splitAddress, NaturalDocs::NDMarkup->ConvertAmpChars( substr($host, 0, $hostSplit) );
+    push @splitAddress, NaturalDocs::NDMarkup->ConvertAmpChars( substr($host, $hostSplit) );
+
+
+    # Now put it back together again.  We'll use spans to split the text transparently and JavaScript to split and join the link.
+
+    my $output =
+    "<a href=\"#\" onClick=\"location.href='mai' + 'lto:' + '" . join("' + '", @splitAddress) . "'; return false;\" class=LEMail>";
+
+    if ($name eq $target)
+        {
+        $output .=
+        $splitAddress[0] . '<span style="display: none">.nosp at m.</span>' . $splitAddress[1]
+        . '<span>@</span>'
+        . $splitAddress[3] . '<span style="display: none">.nosp at m.</span>' . $splitAddress[4];
+        }
+    else
+        {  $output .= $name;  };
+
+    $output .= '</a>';
+    return $output;
+    };
+
+
+#
+#   Function: BuildImage
+#
+#   Builds the HTML for an image.
+#
+#   Parameters:
+#
+#       sourceFile - The source <FileName> this image appears in.
+#       mode - Either "inline" or "link".
+#       target - The target.
+#       original - The original text.
+#
+#       All are assumed to still have <NDMarkup> amp chars.
+#
+#   Returns:
+#
+#       The result in HTML.  If the mode was "link", the target image's HTML is added to <imageContent>.
+#
+sub BuildImage #(sourceFile, mode, target, original)
+    {
+    my ($self, $sourceFile, $mode, $target, $original) = @_;
+
+    my $targetNoAmp = $self->RestoreAmpChars($target);
+
+    my $image = NaturalDocs::ImageReferenceTable->GetReferenceTarget($sourceFile, $targetNoAmp);
+
+    if ($image)
+        {
+        my ($width, $height) = NaturalDocs::Project->ImageFileDimensions($image);
+
+        if ($mode eq 'inline')
+            {
+            return
+            '<img src="' . $self->MakeRelativeURL($self->OutputFileOf($sourceFile),
+                                                                       $self->OutputImageOf($image), 1) . '"'
+
+            . ($width && $height ? ' width="' . $width . '" height="' . $height . '"' : '')
+            . '>';
+            }
+        else # link
+            {
+            # Make the text a little more friendly in the output by removing any folders and file extensions.
+            # (see images/Table1.gif) will be turned into (see Table1).
+            my $originalNoAmp = $self->RestoreAmpChars($original);
+            my $targetIndex = index($originalNoAmp, $targetNoAmp);
+            my ($shortTarget, $shortTargetNoAmp, $shortOriginal);
+
+            if ($targetIndex != -1)
+                {
+                $shortTargetNoAmp = (NaturalDocs::File->SplitPath($targetNoAmp))[2];
+                $shortTargetNoAmp = NaturalDocs::File->NoExtension($shortTargetNoAmp);
+
+                substr($originalNoAmp, $targetIndex, length($targetNoAmp), $shortTargetNoAmp);
+
+                $shortOriginal = NaturalDocs::NDMarkup->ConvertAmpChars($originalNoAmp);
+                $shortTarget = NaturalDocs::NDMarkup->ConvertAmpChars($shortTargetNoAmp);
+                };
+
+            my $output =
+            '<a href="#Image' . $imageAnchorNumber . '" class=CImageLink>'
+                . ($shortOriginal || $original)
+            . '</a>';
+
+            $imageContent .=
+            '<blockquote>'
+            . '<div class=CImage>'
+                . '<a name="Image' . $imageAnchorNumber . '"></a>'
+                . '<div class=CImageCaption>' . ($shortTarget || $target) . '</div>'
+                . '<img src="' . $self->MakeRelativeURL($self->OutputFileOf($sourceFile),
+                                                                           $self->OutputImageOf($image), 1) . '"'
+
+                . ($width && $height ? ' width="' . $width . '" height="' . $height . '"' : '')
+                . '>'
+
+            . '</div></blockquote>';
+
+            $imageAnchorNumber++;
+            return $output;
+            };
+        }
+    else # !$image
+        {
+        if ($mode eq 'inline')
+            {  return '<p>' . $original . '</p>';  }
+        else #($mode eq 'link')
+            {  return $original;  };
+        };
+    };
+
+
+#
+#   Function: BuildToolTipLinkProperties
+#
+#   Returns the properties that should go in the link tag to add a tooltip to it.  Because the function accepts undef, you can
+#   call it without checking if <BuildToolTip()> returned undef or not.
+#
+#   Parameters:
+#
+#       toolTipID - The ID of the tooltip.  If undef, the function will return undef.
+#
+#   Returns:
+#
+#       The properties that should be put in the link tag, or undef if toolTipID wasn't specified.
+#
+sub BuildToolTipLinkProperties #(toolTipID)
+    {
+    my ($self, $toolTipID) = @_;
+
+    if (defined $toolTipID)
+        {
+        my $currentNumber = $tooltipLinkNumber;
+        $tooltipLinkNumber++;
+
+        return 'id=link' . $currentNumber . ' '
+                . 'onMouseOver="ShowTip(event, \'' . $toolTipID . '\', \'link' . $currentNumber . '\')" '
+                . 'onMouseOut="HideTip(\'' . $toolTipID . '\')"';
+        }
+    else
+        {  return undef;  };
+    };
+
+
+#
+#   Function: AddDoubleSpaces
+#
+#   Adds second spaces after the appropriate punctuation with &nbsp; so they show up in HTML.  They don't occur if there isn't at
+#   least one space after the punctuation, so things like class.member notation won't be affected.
+#
+#   Parameters:
+#
+#       text - The text to convert.
+#
+#   Returns:
+#
+#       The text with double spaces as necessary.
+#
+sub AddDoubleSpaces #(text)
+    {
+    my ($self, $text) = @_;
+
+    # Question marks and exclamation points get double spaces unless followed by a lowercase letter.
+
+    $text =~ s/  ([^\ \t\r\n] [\!\?])  # Must appear after a non-whitespace character to apply.
+
+                      (&quot;|&[lr][sd]quo;|[\'\"\]\}\)]?)  # Tolerate closing quotes, parenthesis, etc.
+                      ((?:<[^>]+>)*)  # Tolerate tags
+
+                      \   # The space
+                      (?![a-z])  # Not followed by a lowercase character.
+
+                   /$1$2$3&nbsp;\ /gx;
+
+
+    # Periods get double spaces if it's not followed by a lowercase letter.  However, if it's followed by a capital letter and the
+    # preceding word is in the list of acceptable abbreviations, it won't get the double space.  Yes, I do realize I am seriously
+    # over-engineering this.
+
+    $text =~ s/  ([^\ \t\r\n]+)  # The word prior to the period.
+
+                      \.
+
+                      (&quot;|&[lr][sd]quo;|[\'\"\]\}\)]?)  # Tolerate closing quotes, parenthesis, etc.
+                      ((?:<[^>]+>)*)  # Tolerate tags
+
+                      \   # The space
+                      ([^a-z])   # The next character, if it's not a lowercase letter.
+
+                  /$1 . '.' . $2 . $3 . MaybeExpand($1, $4) . $4/gex;
+
+    sub MaybeExpand #(leadWord, nextLetter)
+        {
+        my ($leadWord, $nextLetter) = @_;
+
+        if ($nextLetter =~ /^[A-Z]$/ && exists $abbreviations{ lc($leadWord) } )
+            { return ' '; }
+        else
+            { return '&nbsp; '; };
+        };
+
+    return $text;
+    };
+
+
+#
+#   Function: ConvertAmpChars
+#
+#   Converts certain characters to their HTML amp char equivalents.
+#
+#   Parameters:
+#
+#       text - The text to convert.
+#
+#   Returns:
+#
+#       The converted text.
+#
+sub ConvertAmpChars #(text)
+    {
+    my ($self, $text) = @_;
+
+    $text =~ s/&/&amp;/g;
+    $text =~ s/\"/&quot;/g;
+    $text =~ s/</&lt;/g;
+    $text =~ s/>/&gt;/g;
+
+    return $text;
+    };
+
+
+#
+#   Function: RestoreAmpChars
+#
+#   Restores all amp characters to their original state.  This works with both <NDMarkup> amp chars and fancy quotes.
+#
+#   Parameters:
+#
+#       text - The text to convert.
+#
+#   Returns:
+#
+#       The converted text.
+#
+sub RestoreAmpChars #(text)
+    {
+    my ($self, $text) = @_;
+
+    $text = NaturalDocs::NDMarkup->RestoreAmpChars($text);
+    $text =~ s/&[lr]squo;/\'/g;
+    $text =~ s/&[lr]dquo;/\"/g;
+
+    return $text;
+    };
+
+
+#
+#   Function: AddHiddenBreaks
+#
+#   Adds hidden breaks to symbols.  Puts them after symbol and directory separators so long names won't screw up the layout.
+#
+#   Parameters:
+#
+#       string - The string to break.
+#
+#   Returns:
+#
+#       The string with hidden breaks.
+#
+sub AddHiddenBreaks #(string)
+    {
+    my ($self, $string) = @_;
+
+    # \.(?=.{5,}) instead of \. so file extensions don't get breaks.
+    # :+ instead of :: because Mac paths are separated by a : and we want to get those too.
+
+    $string =~ s/(\w(?:\.(?=.{5,})|:+|->|\\|\/))(\w)/$1 . '<wbr>' . $2/ge;
+
+    return $string;
+    };
+
+
+#
+#   Function: FindFirstFile
+#
+#   A function that finds and returns the first file entry in the menu, or undef if none.
+#
+sub FindFirstFile
+    {
+    # Hidden parameter: arrayref
+    # Used for recursion only.
+
+    my ($self, $arrayref) = @_;
+
+    if (!defined $arrayref)
+        {  $arrayref = NaturalDocs::Menu->Content();  };
+
+    foreach my $entry (@$arrayref)
+        {
+        if ($entry->Type() == ::MENU_FILE())
+            {
+            return $entry;
+            }
+        elsif ($entry->Type() == ::MENU_GROUP())
+            {
+            my $result = $self->FindFirstFile($entry->GroupContent());
+            if (defined $result)
+                {  return $result;  };
+            };
+        };
+
+    return undef;
+    };
+
+
+#
+#   Function: ExpandMenu
+#
+#   Determines which groups should be expanded.
+#
+#   Parameters:
+#
+#       sourceFile - The source <FileName> to use if you're looking for a source file.
+#       indexType - The index <TopicType> to use if you're looking for an index.
+#       selectionHierarchy - The <FileName> the menu is being built for.  Does not have to be on the menu itself.
+#       rootLength - The length of the menu's root group, *not* including the contents of subgroups.
+#
+#   Returns:
+#
+#       An arrayref of all the group numbers that should be expanded.  At minimum, it will contain the numbers of the groups
+#       present in <menuSelectionHierarchy>, though it may contain more.
+#
+sub ExpandMenu #(FileName sourceFile, TopicType indexType, NaturalDocs::Menu::Entry[] selectionHierarchy, int rootLength) -> int[] groupsToExpand
+    {
+    my ($self, $sourceFile, $indexType, $menuSelectionHierarchy, $rootLength) = @_;
+
+    my $toExpand = [ ];
+
+
+    # First expand everything in the selection hierarchy.
+
+    my $length = $rootLength;
+
+    foreach my $entry (@$menuSelectionHierarchy)
+        {
+        $length += $menuGroupLengths{$entry};
+        push @$toExpand, $menuGroupNumbers{$entry};
+        };
+
+
+    # Now do multiple passes of group expansion as necessary.  We start from bottomIndex and expand outwards.  We stop going
+    # in a direction if a group there is too long -- we do not skip over it and check later groups as well.  However, if one direction
+    # stops, the other can keep going.
+
+    my $pass = 1;
+    my $hasSubGroups;
+
+    while ($length < MENU_LENGTH_LIMIT)
+        {
+        my $content;
+        my $topIndex;
+        my $bottomIndex;
+
+
+        if ($pass == 1)
+            {
+            # First pass, we expand the selection's siblings.
+
+            if (scalar @$menuSelectionHierarchy)
+                {  $content = $menuSelectionHierarchy->[0]->GroupContent();  }
+            else
+                {  $content = NaturalDocs::Menu->Content();  };
+
+            $bottomIndex = 0;
+
+            while ($bottomIndex < scalar @$content &&
+                     !($content->[$bottomIndex]->Type() == ::MENU_FILE() &&
+                       $content->[$bottomIndex]->Target() eq $sourceFile) &&
+                     !($content->[$bottomIndex]->Type() != ::MENU_INDEX() &&
+                       $content->[$bottomIndex]->Target() eq $indexType) )
+                {  $bottomIndex++;  };
+
+            if ($bottomIndex == scalar @$content)
+                {  $bottomIndex = 0;  };
+            $topIndex = $bottomIndex - 1;
+            }
+
+        elsif ($pass == 2)
+            {
+            # If the section we just expanded had no sub-groups, do another pass trying to expand the parent's sub-groups.  The
+            # net effect is that groups won't collapse as much unnecessarily.  Someone can click on a file in a sub-group and the
+            # groups in the parent will stay open.
+
+            if (!$hasSubGroups && scalar @$menuSelectionHierarchy)
+                {
+                if (scalar @$menuSelectionHierarchy > 1)
+                    {  $content = $menuSelectionHierarchy->[1]->GroupContent();  }
+                else
+                    {  $content = NaturalDocs::Menu->Content();  };
+
+                $bottomIndex = 0;
+
+                while ($bottomIndex < scalar @$content &&
+                         $content->[$bottomIndex] != $menuSelectionHierarchy->[0])
+                    {  $bottomIndex++;  };
+
+                $topIndex = $bottomIndex - 1;
+                $bottomIndex++;  # Increment past our own group.
+                $hasSubGroups = undef;
+                }
+            else
+                {  last;  };
+            }
+
+        # No more passes.
+        else
+            {  last;  };
+
+
+        while ( ($topIndex >= 0 || $bottomIndex < scalar @$content) && $length < MENU_LENGTH_LIMIT)
+            {
+            # We do the bottom first.
+
+            while ($bottomIndex < scalar @$content && $content->[$bottomIndex]->Type() != ::MENU_GROUP())
+                {  $bottomIndex++;  };
+
+            if ($bottomIndex < scalar @$content)
+                {
+                my $bottomEntry = $content->[$bottomIndex];
+                $hasSubGroups = 1;
+
+                if ($length + $menuGroupLengths{$bottomEntry} <= MENU_LENGTH_LIMIT)
+                    {
+                    $length += $menuGroupLengths{$bottomEntry};
+                    push @$toExpand, $menuGroupNumbers{$bottomEntry};
+                    $bottomIndex++;
+                    }
+                else
+                    {  $bottomIndex = scalar @$content;  };
+                };
+
+            # Top next.
+
+            while ($topIndex >= 0 && $content->[$topIndex]->Type() != ::MENU_GROUP())
+                {  $topIndex--;  };
+
+            if ($topIndex >= 0)
+                {
+                my $topEntry = $content->[$topIndex];
+                $hasSubGroups = 1;
+
+                if ($length + $menuGroupLengths{$topEntry} <= MENU_LENGTH_LIMIT)
+                    {
+                    $length += $menuGroupLengths{$topEntry};
+                    push @$toExpand, $menuGroupNumbers{$topEntry};
+                    $topIndex--;
+                    }
+                else
+                    {  $topIndex = -1;  };
+                };
+            };
+
+
+        $pass++;
+        };
+
+    return $toExpand;
+    };
+
+
+#
+#   Function: GetMenuSelectionHierarchy
+#
+#   Finds the sequence of menu groups that contain the current selection.
+#
+#   Parameters:
+#
+#       sourceFile - The source <FileName> to use if you're looking for a source file.
+#       indexType - The index <TopicType> to use if you're looking for an index.
+#
+#   Returns:
+#
+#       An arrayref of the <NaturalDocs::Menu::Entry> objects of each group surrounding the selected menu item.  First entry is the
+#       group immediately encompassing it, and each subsequent entry works its way towards the outermost group.
+#
+sub GetMenuSelectionHierarchy #(FileName sourceFile, TopicType indexType) -> NaturalDocs::Menu::Entry[] selectionHierarchy
+    {
+    my ($self, $sourceFile, $indexType) = @_;
+
+    my $hierarchy = [ ];
+
+    $self->FindMenuSelection($sourceFile, $indexType, $hierarchy, NaturalDocs::Menu->Content());
+
+    return $hierarchy;
+    };
+
+
+#
+#   Function: FindMenuSelection
+#
+#   A recursive function that deterimes if it or any of its sub-groups has the menu selection.
+#
+#   Parameters:
+#
+#       sourceFile - The source <FileName> to use if you're looking for a source file.
+#       indexType - The index <TopicType> to use if you're looking for an index.
+#       hierarchyRef - A reference to the menu selection hierarchy.
+#       entries - An arrayref of <NaturalDocs::Menu::Entries> to search.
+#
+#   Returns:
+#
+#       Whether this group or any of its subgroups had the selection.  If true, it will add any subgroups to the menu selection
+#       hierarchy but not itself.  This prevents the topmost entry from being added.
+#
+sub FindMenuSelection #(FileName sourceFile, TopicType indexType, NaturalDocs::Menu::Entry[] hierarchyRef, NaturalDocs::Menu::Entry[] entries) -> bool hasSelection
+    {
+    my ($self, $sourceFile, $indexType, $hierarchyRef, $entries) = @_;
+
+    foreach my $entry (@$entries)
+        {
+        if ($entry->Type() == ::MENU_GROUP())
+            {
+            # If the subgroup has the selection...
+            if ( $self->FindMenuSelection($sourceFile, $indexType, $hierarchyRef, $entry->GroupContent()) )
+                {
+                push @$hierarchyRef, $entry;
+                return 1;
+                };
+            }
+
+        elsif ($entry->Type() == ::MENU_FILE())
+            {
+            if ($sourceFile eq $entry->Target())
+                {  return 1;  };
+            }
+
+        elsif ($entry->Type() == ::MENU_INDEX())
+            {
+            if ($indexType eq $entry->Target)
+                {  return 1;  };
+            };
+        };
+
+    return 0;
+    };
+
+
+#
+#   Function: ResetToolTips
+#
+#   Resets the <ToolTip Package Variables> for a new page.
+#
+#   Parameters:
+#
+#       samePage  - Set this flag if there's the possibility that the next batch of tooltips may be on the same page as the last.
+#
+sub ResetToolTips #(samePage)
+    {
+    my ($self, $samePage) = @_;
+
+    if (!$samePage)
+        {
+        $tooltipLinkNumber = 1;
+        $tooltipNumber = 1;
+        };
+
+    $tooltipHTML = undef;
+    %tooltipSymbolsToNumbers = ( );
+    };
+
+
+1;

Added: trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Builder.pm
===================================================================
--- trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Builder.pm	                        (rev 0)
+++ trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Builder.pm	2009-07-01 15:30:00 UTC (rev 4275)
@@ -0,0 +1,280 @@
+###############################################################################
+#
+#   Package: NaturalDocs::Builder
+#
+###############################################################################
+#
+#   A package that takes parsed source file and builds the output for it.
+#
+#   Usage and Dependencies:
+#
+#       - <Add()> can be called immediately.
+#       - <OutputPackages()> and <OutputPackageOf()> can be called once all sub-packages have been registered via <Add()>.
+#         Since this is normally done in their INIT functions, they should be available to all normal functions immediately.
+#
+#       - Prior to calling <Run()>, <NaturalDocs::Settings>, <NaturalDocs::Project>, <NaturalDocs::Menu>, and
+#         <NaturalDocs::Parser> must be initialized.  <NaturalDocs::Settings->GenerateDirectoryNames()> must be called.
+#         <NaturalDocs::SymbolTable> and <NaturalDocs::ClassHierarchy> must be initialized and fully resolved.
+#
+###############################################################################
+
+# 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;
+
+use NaturalDocs::Builder::Base;
+use NaturalDocs::Builder::HTML;
+use NaturalDocs::Builder::FramedHTML;
+
+package NaturalDocs::Builder;
+
+
+###############################################################################
+# Group: Variables
+
+#
+#   Array: outputPackages
+#
+#   An array of the output packages available for use.
+#
+my @outputPackages;
+
+
+###############################################################################
+# Group: Functions
+
+
+#
+#   Function: OutputPackages
+#
+#   Returns an arrayref of the output packages available for use.  The arrayref is not a copy of the data, so don't change it.
+#
+#   Add output packages to this list with the <Add()> function.
+#
+sub OutputPackages
+    {  return \@outputPackages;  };
+
+
+#
+#   Function: OutputPackageOf
+#
+#   Returns the output package corresponding to the passed command line option, or undef if none.
+#
+sub OutputPackageOf #(commandLineOption)
+    {
+    my ($self, $commandLineOption) = @_;
+
+    $commandLineOption = lc($commandLineOption);
+
+    foreach my $package (@outputPackages)
+        {
+        if (lc($package->CommandLineOption()) eq $commandLineOption)
+            {  return $package;  };
+        };
+
+    return undef;
+    };
+
+
+
+#
+#   Function: Add
+#
+#   Adds an output package to those available for use.  All output packages must call this function in order to be recognized.
+#
+#   Parameters:
+#
+#       package - The package name.
+#
+sub Add #(package)
+    {
+    my ($self, $package) = @_;
+
+    # Output packages shouldn't register themselves more than once, so we don't need to check for it.
+    push @outputPackages, $package;
+    };
+
+
+#
+#   Function: Run
+#
+#   Runs the build process.  This must be called *every time* Natural Docs is run, regardless of whether any source files changed
+#   or not.  Some output packages have dependencies on files outside of the source tree that need to be checked.
+#
+#   Since there are multiple stages to the build process, this function will handle its own status messages.  There's no need to print
+#   "Building files..." or something similar beforehand.
+#
+sub Run
+    {
+    my ($self) = @_;
+
+
+    # Determine what we're doing.
+
+    my $buildTargets = NaturalDocs::Settings->BuildTargets();
+
+    my $filesToBuild = NaturalDocs::Project->FilesToBuild();
+    my $numberOfFilesToBuild = (scalar keys %$filesToBuild) * (scalar @$buildTargets);
+
+    my $filesToPurge = NaturalDocs::Project->FilesToPurge();
+    my $numberOfFilesToPurge = (scalar keys %$filesToPurge) * (scalar @$buildTargets);
+
+    my $imagesToUpdate = NaturalDocs::Project->ImageFilesToUpdate();
+    my $numberOfImagesToUpdate = (scalar keys %$imagesToUpdate) * (scalar @$buildTargets);
+
+    my $imagesToPurge = NaturalDocs::Project->ImageFilesToPurge();
+    my $numberOfImagesToPurge = (scalar keys %$imagesToPurge) * (scalar @$buildTargets);
+
+    my %indexesToBuild;
+    my %indexesToPurge;
+
+    my $currentIndexes = NaturalDocs::Menu->Indexes();
+    my $previousIndexes = NaturalDocs::Menu->PreviousIndexes();
+
+    foreach my $index (keys %$currentIndexes)
+        {
+        if (NaturalDocs::SymbolTable->IndexChanged($index) || !exists $previousIndexes->{$index})
+            {
+            $indexesToBuild{$index} = 1;
+            };
+        };
+
+    # All indexes that still exist should have been deleted.
+    foreach my $index (keys %$previousIndexes)
+        {
+        if (!exists $currentIndexes->{$index})
+            {
+            $indexesToPurge{$index} = 1;
+            };
+        };
+
+    my $numberOfIndexesToBuild = (scalar keys %indexesToBuild) * (scalar @$buildTargets);
+    my $numberOfIndexesToPurge = (scalar keys %indexesToPurge) * (scalar @$buildTargets);
+
+
+    # Start the build process
+
+    foreach my $buildTarget (@$buildTargets)
+        {
+        $buildTarget->Builder()->BeginBuild( $numberOfFilesToBuild || $numberOfFilesToPurge ||
+                                                               $numberOfImagesToUpdate || $numberOfImagesToPurge ||
+                                                               $numberOfIndexesToBuild || $numberOfIndexesToPurge ||
+                                                               NaturalDocs::Menu->HasChanged() );
+        };
+
+    if ($numberOfFilesToPurge)
+        {
+        NaturalDocs::StatusMessage->Start('Purging ' . $numberOfFilesToPurge
+                                                          . ' file' . ($numberOfFilesToPurge > 1 ? 's' : '') . '...',
+                                                             scalar @$buildTargets);
+
+        foreach my $buildTarget (@$buildTargets)
+            {
+            $buildTarget->Builder()->PurgeFiles($filesToPurge);
+            NaturalDocs::StatusMessage->CompletedItem();
+            };
+        };
+
+    if ($numberOfIndexesToPurge)
+        {
+        NaturalDocs::StatusMessage->Start('Purging ' . $numberOfIndexesToPurge
+                                                           . ' index' . ($numberOfIndexesToPurge > 1 ? 'es' : '') . '...',
+                                                             scalar @$buildTargets);
+
+        foreach my $buildTarget (@$buildTargets)
+            {
+            $buildTarget->Builder()->PurgeIndexes(\%indexesToPurge);
+            NaturalDocs::StatusMessage->CompletedItem();
+            };
+        };
+
+    if ($numberOfImagesToPurge)
+        {
+        NaturalDocs::StatusMessage->Start('Purging ' . $numberOfImagesToPurge
+                                                          . ' image' . ($numberOfImagesToPurge > 1 ? 's' : '') . '...',
+                                                             scalar @$buildTargets);
+
+        foreach my $buildTarget (@$buildTargets)
+            {
+            $buildTarget->Builder()->PurgeImages($imagesToPurge);
+            NaturalDocs::StatusMessage->CompletedItem();
+            };
+        };
+
+    if ($numberOfFilesToBuild)
+        {
+        NaturalDocs::StatusMessage->Start('Building ' . $numberOfFilesToBuild
+                                                           . ' file' . ($numberOfFilesToBuild > 1 ? 's' : '') . '...',
+                                                             $numberOfFilesToBuild);
+
+        foreach my $file (keys %$filesToBuild)
+            {
+            my $parsedFile = NaturalDocs::Parser->ParseForBuild($file);
+
+            NaturalDocs::Error->OnStartBuilding($file);
+
+            foreach my $buildTarget (@$buildTargets)
+                {
+                $buildTarget->Builder()->BuildFile($file, $parsedFile);
+                NaturalDocs::StatusMessage->CompletedItem();
+                };
+
+            NaturalDocs::Error->OnEndBuilding($file);
+            };
+        };
+
+    if ($numberOfIndexesToBuild)
+        {
+        NaturalDocs::StatusMessage->Start('Building ' . $numberOfIndexesToBuild
+                                                          . ' index' . ($numberOfIndexesToBuild > 1 ? 'es' : '') . '...',
+                                                             $numberOfIndexesToBuild);
+
+        foreach my $index (keys %indexesToBuild)
+            {
+            foreach my $buildTarget (@$buildTargets)
+                {
+                $buildTarget->Builder()->BuildIndex($index);
+                NaturalDocs::StatusMessage->CompletedItem();
+                };
+            };
+        };
+
+    if ($numberOfImagesToUpdate)
+        {
+        NaturalDocs::StatusMessage->Start('Updating ' . $numberOfImagesToUpdate
+                                                          . ' image' . ($numberOfImagesToUpdate > 1 ? 's' : '') . '...',
+                                                             $numberOfImagesToUpdate);
+
+        foreach my $image (keys %$imagesToUpdate)
+            {
+            foreach my $buildTarget (@$buildTargets)
+                {
+                $buildTarget->Builder()->UpdateImage($image);
+                NaturalDocs::StatusMessage->CompletedItem();
+                };
+            };
+        };
+
+    if (NaturalDocs::Menu->HasChanged())
+        {
+        if (!NaturalDocs::Settings->IsQuiet())
+            {  print "Updating menu...\n";  };
+
+        foreach my $buildTarget (@$buildTargets)
+            {  $buildTarget->Builder()->UpdateMenu();  };
+        };
+
+    foreach my $buildTarget (@$buildTargets)
+        {
+        $buildTarget->Builder()->EndBuild($numberOfFilesToBuild || $numberOfFilesToPurge ||
+                                                           $numberOfIndexesToBuild || $numberOfIndexesToPurge ||
+                                                           $numberOfImagesToUpdate || $numberOfImagesToPurge ||
+                                                           NaturalDocs::Menu->HasChanged());
+        };
+    };
+
+
+1;

Added: trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/ClassHierarchy/Class.pm
===================================================================
--- trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/ClassHierarchy/Class.pm	                        (rev 0)
+++ trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/ClassHierarchy/Class.pm	2009-07-01 15:30:00 UTC (rev 4275)
@@ -0,0 +1,412 @@
+###############################################################################
+#
+#   Class: NaturalDocs::ClassHierarchy::Class
+#
+###############################################################################
+#
+#   An object that stores information about a class in the hierarchy.  It does not store its <SymbolString>; it assumes that it will
+#   be stored in a hashref where the key is the <SymbolString>.
+#
+###############################################################################
+
+# 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::ClassHierarchy::Class;
+
+
+#
+#   Constants: Members
+#
+#   The class is implemented as a blessed arrayref.  The keys are the constants below.
+#
+#   DEFINITIONS - An existence hashref of all the <FileNames> which define this class.  Undef if none.
+#   PARENTS - An existence hashref of the <SymbolStrings> of all the parents this class has.
+#   CHILDREN - An existence hashref of the <SymbolStrings> of all the children this class has.
+#   PARENT_REFERENCES - A hashref of the parent <ReferenceStrings> this class has.  The keys are the <ReferenceStrings>,
+#                                      and the values are existence hashrefs of all the <FileNames> that define them.  Undef if none.
+#
+use NaturalDocs::DefineMembers 'DEFINITIONS', 'PARENTS', 'CHILDREN', 'PARENT_REFERENCES';
+# Dependency: New() depends on the order of these constants, as well as the class not being derived from any other.
+
+
+###############################################################################
+# Group: Modification Functions
+
+
+#
+#   Function: New
+#
+#   Creates and returns a new class.
+#
+sub New
+    {
+    # Dependency: This function depends on the order of the constants, as well as the class not being derived from any other.
+    my ($package, $definitionFile) = @_;
+
+    my $object = [ undef, undef, undef, undef ];
+    bless $object, $package;
+
+    return $object;
+    };
+
+
+#
+#   Function: AddDefinition
+#
+#   Adds a rew definition of this class and returns if that was the first definition.
+#
+#   Parameters:
+#
+#       file - The <FileName> the definition appears in.
+#
+#   Returns:
+#
+#       Whether this was the first definition of this class.
+#
+sub AddDefinition #(file)
+    {
+    my ($self, $file) = @_;
+
+    my $wasFirst;
+
+    if (!defined $self->[DEFINITIONS])
+        {
+        $self->[DEFINITIONS] = { };
+        $wasFirst = 1;
+        };
+
+    $self->[DEFINITIONS]->{$file} = 1;
+
+    return $wasFirst;
+    };
+
+
+#
+#   Function: DeleteDefinition
+#
+#   Removes the definition of this class and returns if there are no more definitions.  Note that if there are no more
+#   definitions, you may still want to keep the object around if <HasChildren()> returns true.
+#
+#   Parameters:
+#
+#       file - The <FileName> the definition appears in.
+#
+#   Returns:
+#
+#       Whether this deleted the last definition of this class.
+#
+sub DeleteDefinition #(file)
+    {
+    my ($self, $file) = @_;
+
+    if (defined $self->[DEFINITIONS])
+        {
+        delete $self->[DEFINITIONS]->{$file};
+
+        if (!scalar keys %{$self->[DEFINITIONS]})
+            {
+            $self->[DEFINITIONS] = undef;
+            return 1;
+            };
+        };
+
+    return undef;
+    };
+
+
+#
+#   Function: AddParentReference
+#
+#   Adds a parent reference to the class and return whether it resulted in a new parent class.
+#
+#   Parameters:
+#
+#       reference - The <ReferenceString> used to determine the parent.
+#       file - The <FileName> the parent reference is in.
+#       referenceTranslations - A hashref of what each reference currently resolves to.  The keys are the
+#                                         <ReferenceStrings> and the values are class <SymbolStrings>.  It should include an entry for
+#                                         the reference parameter above.
+#
+#   Returns:
+#
+#       If the reference adds a new parent, it will return that parent's <SymbolString>.  Otherwise it will return undef.
+#
+sub AddParentReference #(reference, file, referenceTranslations)
+    {
+    my ($self, $reference, $file, $referenceTranslations) = @_;
+
+    if (!defined $self->[PARENT_REFERENCES])
+        {  $self->[PARENT_REFERENCES] = { };  };
+    if (!defined $self->[PARENTS])
+        {  $self->[PARENTS] = { };  };
+
+
+    if (!exists $self->[PARENT_REFERENCES]->{$reference})
+        {
+        $self->[PARENT_REFERENCES]->{$reference} = { $file => 1 };
+
+        my $symbol = $referenceTranslations->{$reference};
+
+        if (!exists $self->[PARENTS]->{$symbol})
+            {
+            $self->[PARENTS]->{$symbol} = 1;
+            return $symbol;
+            }
+        else
+            {  return undef;  };
+        }
+    else
+        {
+        $self->[PARENT_REFERENCES]->{$reference}->{$file} = 1;
+        return undef;
+        };
+    };
+
+#
+#   Function: DeleteParentReference
+#
+#   Deletes a parent reference from the class and return whether it resulted in a loss of a parent class.
+#
+#   Parameters:
+#
+#       reference - The <ReferenceString> used to determine the parent.
+#       file - The <FileName> the parent declaration is in.
+#       referenceTranslations - A hashref of what each reference currently resolves to.  The keys are the
+#                                         <ReferenceStrings> and the values are class <SymbolStrings>.  It should include an entry for
+#                                         the reference parameter above.
+#
+#   Returns:
+#
+#       If this causes a parent class to be lost, it will return that parent's <SymbolString>.  Otherwise it will return undef.
+#
+sub DeleteParentReference #(reference, file, referenceTranslations)
+    {
+    my ($self, $reference, $file, $referenceTranslations) = @_;
+
+    if (defined $self->[PARENT_REFERENCES] && exists $self->[PARENT_REFERENCES]->{$reference} &&
+        exists $self->[PARENT_REFERENCES]->{$reference}->{$file})
+        {
+        delete $self->[PARENT_REFERENCES]->{$reference}->{$file};
+
+        # Quit if there are other definitions of this reference.
+        if (scalar keys %{$self->[PARENT_REFERENCES]->{$reference}})
+            {  return undef;  };
+
+        delete $self->[PARENT_REFERENCES]->{$reference};
+
+        if (!scalar keys %{$self->[PARENT_REFERENCES]})
+            {  $self->[PARENT_REFERENCES] = undef;  };
+
+        my $parent = $referenceTranslations->{$reference};
+
+        # Check if any other references resolve to the same parent.
+        if (defined $self->[PARENT_REFERENCES])
+            {
+            foreach my $parentReference (keys %{$self->[PARENT_REFERENCES]})
+                {
+                if ($referenceTranslations->{$parentReference} eq $parent)
+                    {  return undef;  };
+                };
+            };
+
+        # If we got this far, no other parent references resolve to this symbol.
+
+        delete $self->[PARENTS]->{$parent};
+
+        if (!scalar keys %{$self->[PARENTS]})
+            {  $self->[PARENTS] = undef;  };
+
+        return $parent;
+        }
+    else
+        {  return undef;  };
+    };
+
+
+#
+#   Function: AddChild
+#   Adds a child <SymbolString> to the class.  Unlike <AddParentReference()>, this does not keep track of anything other than
+#   whether it has it or not.
+#
+#   Parameters:
+#
+#       child - The <SymbolString> to add.
+#
+sub AddChild #(child)
+    {
+    my ($self, $child) = @_;
+
+    if (!defined $self->[CHILDREN])
+        {  $self->[CHILDREN] = { };  };
+
+    $self->[CHILDREN]->{$child} = 1;
+    };
+
+#
+#   Function: DeleteChild
+#   Deletes a child <SymbolString> from the class.  Unlike <DeleteParentReference()>, this does not keep track of anything other
+#   than whether it has it or not.
+#
+#   Parameters:
+#
+#       child - The <SymbolString> to delete.
+#
+sub DeleteChild #(child)
+    {
+    my ($self, $child) = @_;
+
+    if (defined $self->[CHILDREN])
+        {
+        delete $self->[CHILDREN]->{$child};
+
+        if (!scalar keys %{$self->[CHILDREN]})
+            {  $self->[CHILDREN] = undef;  };
+        };
+    };
+
+
+
+###############################################################################
+# Group: Information Functions
+
+#
+#   Function: Definitions
+#   Returns an array of the <FileNames> that define this class, or an empty array if none.
+#
+sub Definitions
+    {
+    my ($self) = @_;
+
+    if (defined $self->[DEFINITIONS])
+        {  return keys %{$self->[DEFINITIONS]};  }
+    else
+        {  return ( );  };
+    };
+
+#
+#   Function: IsDefinedIn
+#   Returns whether the class is defined in the passed <FileName>.
+#
+sub IsDefinedIn #(file)
+    {
+    my ($self, $file) = @_;
+
+    if (defined $self->[DEFINITIONS])
+        {  return exists $self->[DEFINITIONS]->{$file};  }
+    else
+        {  return 0;  };
+    };
+
+#
+#   Function: IsDefined
+#   Returns whether the class is defined in any files.
+#
+sub IsDefined
+    {
+    my ($self) = @_;
+    return defined $self->[DEFINITIONS];
+    };
+
+#
+#   Function: ParentReferences
+#   Returns an array of the parent <ReferenceStrings>, or an empty array if none.
+#
+sub ParentReferences
+    {
+    my ($self) = @_;
+
+    if (defined $self->[PARENT_REFERENCES])
+        {  return keys %{$self->[PARENT_REFERENCES]};  }
+    else
+        {  return ( );  };
+    };
+
+#
+#   Function: HasParentReference
+#   Returns whether the class has the passed parent <ReferenceString>.
+#
+sub HasParentReference #(reference)
+    {
+    my ($self, $reference) = @_;
+    return (defined $self->[PARENT_REFERENCES] && exists $self->[PARENT_REFERENCES]->{$reference});
+    };
+
+#
+#   Function: HasParentReferences
+#   Returns whether the class has any parent <ReferenceStrings>.
+#
+sub HasParentReferences
+    {
+    my ($self) = @_;
+    return defined $self->[PARENT_REFERENCES];
+    };
+
+#
+#   Function: Parents
+#   Returns an array of the parent <SymbolStrings>, or an empty array if none.
+#
+sub Parents
+    {
+    my ($self) = @_;
+
+    if (defined $self->[PARENTS])
+        {  return keys %{$self->[PARENTS]};  }
+    else
+        {  return ( );  };
+    };
+
+#
+#   Function: HasParents
+#   Returns whether the class has any parent <SymbolStrings> defined.
+#
+sub HasParents
+    {
+    my ($self) = @_;
+    return defined $self->[PARENTS];
+    };
+
+#
+#   Function: Children
+#   Returns an array of the child <SymbolStrings>, or an empty array if none.
+#
+sub Children
+    {
+    my ($self) = @_;
+
+    if (defined $self->[CHILDREN])
+        {  return keys %{$self->[CHILDREN]};  }
+    else
+        {  return ( );  };
+    };
+
+#
+#   Function: HasChildren
+#   Returns whether any child <SymbolStrings> are defined.
+#
+sub HasChildren
+    {
+    my ($self) = @_;
+    return defined $self->[CHILDREN];
+    };
+
+
+#
+#   Function: ParentReferenceDefinitions
+#   Returns an array of the <FileNames> which define the passed parent <ReferenceString>, or an empty array if none.
+#
+sub ParentReferenceDefinitions #(reference)
+    {
+    my ($self, $reference) = @_;
+
+    if (defined $self->[PARENT_REFERENCES] && exists $self->[PARENT_REFERENCES]->{$reference})
+        {  return keys %{$self->[PARENT_REFERENCES]->{$reference}};  }
+    else
+        {  return ( );  };
+    };
+
+
+1;

Added: trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/ClassHierarchy/File.pm
===================================================================
--- trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/ClassHierarchy/File.pm	                        (rev 0)
+++ trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/ClassHierarchy/File.pm	2009-07-01 15:30:00 UTC (rev 4275)
@@ -0,0 +1,157 @@
+###############################################################################
+#
+#   Class: NaturalDocs::ClassHierarchy::File
+#
+###############################################################################
+#
+#   An object that stores information about what hierarchy information is present in a file.  It does not store its <FileName>; it
+#   assumes that it will be stored in a hashref where the key is the <FileName>.
+#
+###############################################################################
+
+# 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::ClassHierarchy::File;
+
+
+#
+#   Topic: Implementation
+#
+#   Since there's only one member in the class, and it's a hashref, the class is simply the hashref itself blessed as a class.
+#   The keys are the class <SymbolStrings> that are defined in the file, and the values are existence hashrefs of each class'
+#   parent <ReferenceStrings>, or undef if none.
+#
+
+
+###############################################################################
+# Group: Modification Functions
+
+
+#
+#   Function: New
+#
+#   Creates and returns a new class.
+#
+sub New
+    {
+    my ($package) = @_;
+
+    my $object = { };
+    bless $object, $package;
+
+    return $object;
+    };
+
+#
+#   Function: AddClass
+#   Adds a rew class <SymbolString> to the file.
+#
+sub AddClass #(class)
+    {
+    my ($self, $class) = @_;
+
+    if (!exists $self->{$class})
+        {  $self->{$class} = undef;  };
+    };
+
+#
+#   Function: DeleteClass
+#   Deletes a class <SymbolString> from the file.
+#
+sub DeleteClass #(class)
+    {
+    my ($self, $class) = @_;
+    delete $self->{$class};
+    };
+
+#
+#   Function: AddParentReference
+#   Adds a parent <ReferenceString> to a class <SymbolString>.
+#
+sub AddParentReference #(class, parentReference)
+    {
+    my ($self, $class, $parent) = @_;
+
+    if (!exists $self->{$class} || !defined $self->{$class})
+        {  $self->{$class} = { };  };
+
+    $self->{$class}->{$parent} = 1;
+    };
+
+#
+#   Function: DeleteParentReference
+#   Deletes a parent <ReferenceString> from a class <SymbolString>.
+#
+sub DeleteParentReference #(class, parent)
+    {
+    my ($self, $class, $parent) = @_;
+
+    if (exists $self->{$class})
+        {
+        delete $self->{$class}->{$parent};
+
+        if (!scalar keys %{$self->{$class}})
+            {  $self->{$class} = undef;  };
+        };
+    };
+
+
+
+###############################################################################
+# Group: Information Functions
+
+
+#
+#   Function: Classes
+#   Returns an array of the class <SymbolStrings> that are defined by this file, or an empty array if none.
+#
+sub Classes
+    {
+    my ($self) = @_;
+    return keys %{$self};
+    };
+
+#
+#   Function: HasClass
+#   Returns whether the file defines the passed class <SymbolString>.
+#
+sub HasClass #(class)
+    {
+    my ($self, $class) = @_;
+    return exists $self->{$class};
+    };
+
+#
+#   Function: ParentReferencesOf
+#   Returns an array of the parent <ReferenceStrings> that are defined by the class, or an empty array if none.
+#
+sub ParentReferencesOf #(class)
+    {
+    my ($self, $class) = @_;
+
+    if (!exists $self->{$class} || !defined $self->{$class})
+        {  return ( );  }
+    else
+        {  return keys %{$self->{$class}};  };
+    };
+
+#
+#   Function: HasParentReference
+#   Returns whether the file defines the passed class <SymbolString> and parent <ReferenceString>.
+#
+sub HasParentReference #(class, parent)
+    {
+    my ($self, $class, $parent) = @_;
+
+    if (!$self->HasClass($class))
+        {  return undef;  };
+
+    return exists $self->{$class}->{$parent};
+    };
+
+
+1;

Added: trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/ClassHierarchy.pm
===================================================================
--- trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/ClassHierarchy.pm	                        (rev 0)
+++ trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/ClassHierarchy.pm	2009-07-01 15:30:00 UTC (rev 4275)
@@ -0,0 +1,860 @@
+###############################################################################
+#
+#   Package: NaturalDocs::ClassHierarchy
+#
+###############################################################################
+#
+#   A package that handles all the gory details of managing the class hierarchy.  It handles the hierarchy itself, which files define
+#   them, rebuilding the files that are affected by changes, and loading and saving them to a file.
+#
+#   Usage and Dependencies:
+#
+#       - <NaturalDocs::Settings> and <NaturalDocs::Project> must be initialized before use.
+#
+#       - <NaturalDocs::SymbolTable> must be initialized before <Load()> is called.  It must reflect the state as of the last time
+#          Natural Docs was run.
+#
+#       - <Load()> must be called to initialize the package.  At this point, the <Information Functions> will return the state as
+#         of the last time Natural Docs was run.  You are free to resolve <NaturalDocs::SymbolTable()> afterwards.
+#
+#       - <Purge()> must be called, and then <NaturalDocs::Parser->ParseForInformation()> must be called on all files that
+#         have changed so it can fully resolve the hierarchy via the <Modification Functions()>.  Afterwards the
+#         <Information Functions> will reflect the current state of the code.
+#
+#       - <Save()> must be called to commit any changes to the symbol table back to disk.
+#
+###############################################################################
+
+# 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;
+
+use NaturalDocs::ClassHierarchy::Class;
+use NaturalDocs::ClassHierarchy::File;
+
+package NaturalDocs::ClassHierarchy;
+
+
+###############################################################################
+# Group: Variables
+
+#
+#   handle: CLASS_HIERARCHY_FILEHANDLE
+#   The file handle used with <ClassHierarchy.nd>.
+#
+
+#
+#   hash: classes
+#
+#   A hash of all the classes.  The keys are the class <SymbolStrings> and the values are <NaturalDocs::ClassHierarchy::Classes>.
+#
+my %classes;
+
+#
+#   hash: files
+#
+#   A hash of the hierarchy information referenced by file.  The keys are the <FileNames>, and the values are
+#   <NaturalDocs::ClassHierarchy::File>s.
+#
+my %files;
+
+#
+#   hash: parentReferences
+#
+#   A hash of all the parent reference strings and what they resolve to.  The keys are the <ReferenceStrings> and the values are
+#   the class <SymbolStrings> that they resolve to.
+#
+my %parentReferences;
+
+#
+#   object: watchedFile
+#
+#   A <NaturalDocs::ClassHierarchy::File> object of the file being watched for changes.  This is compared to the version in <files>
+#   to see if anything was changed since the last parse.
+#
+my $watchedFile;
+
+#
+#   string: watchedFileName
+#
+#   The <FileName> of the watched file, if any.  If there is no watched file, this will be undef.
+#
+my $watchedFileName;
+
+#
+#   bool: dontRebuildFiles
+#
+#   A bool to set if you don't want changes in the hierarchy to cause files to be rebuilt.
+#
+my $dontRebuildFiles;
+
+
+
+###############################################################################
+# Group: Files
+
+
+#
+#   File: ClassHierarchy.nd
+#
+#   Stores the class hierarchy on disk.
+#
+#   Format:
+#
+#       > [BINARY_FORMAT]
+#       > [VersionInt: app version]
+#
+#       The standard <BINARY_FORMAT> and <VersionInt> header.
+#
+#       > [SymbolString: class or undef to end]
+#
+#       Next we begin a class segment with its <SymbolString>.  These continue until the end of the file.  Only defined classes are
+#       included.
+#
+#       > [UInt32: number of files]
+#       > [AString16: file] [AString16: file] ...
+#
+#       Next there is the number of files that define that class.  It's a UInt32, which seems like overkill, but I could imagine every
+#       file in a huge C++ project being under the same namespace, and thus contributing its own definition.  It's theoretically
+#       possible.
+#
+#       Following the number is that many file names.  You must remember the index of each file, as they will be important later.
+#       Indexes start at one because zero has a special meaning.
+#
+#       > [UInt8: number of parents]
+#       > ( [ReferenceString (no type): parent]
+#       >   [UInt32: file index] [UInt32: file index] ... [UInt32: 0] ) ...
+#
+#       Next there is the number of parents defined for this class.  For each one, we define a parent segment, which consists of
+#       its <ReferenceString>, and then a zero-terminated string of indexes of the files that define that parent as part of that class.
+#       The indexes start at one, and are into the list of files we saw previously.
+#
+#       Note that we do store class segments for classes without parents, but not for undefined classes.
+#
+#       This concludes a class segment.  These segments continue until an undef <SymbolString>.
+#
+#   See Also:
+#
+#       <File Format Conventions>
+#
+#   Revisions:
+#
+#       1.22:
+#
+#           - Classes and parents switched from AString16s to <SymbolStrings> and <ReferenceStrings>.
+#           - A ending undef <SymbolString> was added to the end.  Previously it stopped when the file ran out.
+#
+#       1.2:
+#
+#           - This file was introduced in 1.2.
+#
+
+
+###############################################################################
+# Group: File Functions
+
+
+#
+#   Function: Load
+#
+#   Loads the class hierarchy from disk.
+#
+sub Load
+    {
+    my ($self) = @_;
+
+    $dontRebuildFiles = 1;
+
+    my $fileIsOkay;
+    my $fileName = NaturalDocs::Project->DataFile('ClassHierarchy.nd');
+
+    if (!NaturalDocs::Settings->RebuildData() && open(CLASS_HIERARCHY_FILEHANDLE, '<' . $fileName))
+        {
+        # See if it's binary.
+        binmode(CLASS_HIERARCHY_FILEHANDLE);
+
+        my $firstChar;
+        read(CLASS_HIERARCHY_FILEHANDLE, $firstChar, 1);
+
+        if ($firstChar != ::BINARY_FORMAT())
+            {
+            close(CLASS_HIERARCHY_FILEHANDLE);
+            }
+        else
+            {
+            my $version = NaturalDocs::Version->FromBinaryFile(\*CLASS_HIERARCHY_FILEHANDLE);
+
+            # Minor bugs were fixed in 1.33 that may affect the stored data.
+
+            if (NaturalDocs::Version->CheckFileFormat( $version, NaturalDocs::Version->FromString('1.33') ))
+                {  $fileIsOkay = 1;  }
+            else
+                {  close(CLASS_HIERARCHY_FILEHANDLE);  };
+            };
+        };
+
+
+    if (!$fileIsOkay)
+        {
+        NaturalDocs::Project->ReparseEverything();
+        }
+    else
+        {
+        my $raw;
+
+        for (;;)
+            {
+            # [SymbolString: class or undef to end]
+
+            my $class = NaturalDocs::SymbolString->FromBinaryFile(\*CLASS_HIERARCHY_FILEHANDLE);
+
+            if (!defined $class)
+                {  last;  };
+
+            # [UInt32: number of files]
+
+            read(CLASS_HIERARCHY_FILEHANDLE, $raw, 4);
+            my $numberOfFiles = unpack('N', $raw);
+
+            my @files;
+
+            while ($numberOfFiles)
+                {
+                # [AString16: file]
+
+                read(CLASS_HIERARCHY_FILEHANDLE, $raw, 2);
+                my $fileLength = unpack('n', $raw);
+
+                my $file;
+                read(CLASS_HIERARCHY_FILEHANDLE, $file, $fileLength);
+
+                push @files, $file;
+                $self->AddClass($file, $class, NaturalDocs::Languages->LanguageOf($file)->Name());
+
+                $numberOfFiles--;
+                };
+
+            # [UInt8: number of parents]
+
+            read(CLASS_HIERARCHY_FILEHANDLE, $raw, 1);
+            my $numberOfParents = unpack('C', $raw);
+
+            while ($numberOfParents)
+                {
+                # [ReferenceString (no type): parent]
+
+                my $parent = NaturalDocs::ReferenceString->FromBinaryFile(\*CLASS_HIERARCHY_FILEHANDLE,
+                                                                                                         ::BINARYREF_NOTYPE(),
+                                                                                                         ::REFERENCE_CH_PARENT());
+
+                for (;;)
+                    {
+                    # [UInt32: file index or 0]
+
+                    read(CLASS_HIERARCHY_FILEHANDLE, $raw, 4);
+                    my $fileIndex = unpack('N', $raw);
+
+                    if ($fileIndex == 0)
+                        {  last;  }
+
+                    $self->AddParentReference( $files[$fileIndex - 1], $class, $parent );
+                    };
+
+                $numberOfParents--;
+                };
+            };
+
+        close(CLASS_HIERARCHY_FILEHANDLE);
+        };
+
+    $dontRebuildFiles = undef;
+    };
+
+
+#
+#   Function: Save
+#
+#   Saves the class hierarchy to disk.
+#
+sub Save
+    {
+    my ($self) = @_;
+
+    open (CLASS_HIERARCHY_FILEHANDLE, '>' . NaturalDocs::Project->DataFile('ClassHierarchy.nd'))
+        or die "Couldn't save " . NaturalDocs::Project->DataFile('ClassHierarchy.nd') . ".\n";
+
+    binmode(CLASS_HIERARCHY_FILEHANDLE);
+
+    print CLASS_HIERARCHY_FILEHANDLE '' . ::BINARY_FORMAT();
+    NaturalDocs::Version->ToBinaryFile(\*CLASS_HIERARCHY_FILEHANDLE, NaturalDocs::Settings->AppVersion());
+
+    while (my ($class, $classObject) = each %classes)
+        {
+        if ($classObject->IsDefined())
+            {
+            # [SymbolString: class or undef to end]
+
+            NaturalDocs::SymbolString->ToBinaryFile(\*CLASS_HIERARCHY_FILEHANDLE, $class);
+
+            # [UInt32: number of files]
+
+            my @definitions = $classObject->Definitions();
+            my %definitionIndexes;
+
+            print CLASS_HIERARCHY_FILEHANDLE pack('N', scalar @definitions);
+
+            for (my $i = 0; $i < scalar @definitions; $i++)
+                {
+                # [AString16: file]
+                print CLASS_HIERARCHY_FILEHANDLE pack('nA*', length($definitions[$i]), $definitions[$i]);
+                $definitionIndexes{$definitions[$i]} = $i + 1;
+                };
+
+            # [UInt8: number of parents]
+
+            my @parents = $classObject->ParentReferences();
+            print CLASS_HIERARCHY_FILEHANDLE pack('C', scalar @parents);
+
+            foreach my $parent (@parents)
+                {
+                # [ReferenceString (no type): parent]
+
+                NaturalDocs::ReferenceString->ToBinaryFile(\*CLASS_HIERARCHY_FILEHANDLE, $parent, ::BINARYREF_NOTYPE());
+
+                # [UInt32: file index]
+
+                my @parentDefinitions = $classObject->ParentReferenceDefinitions($parent);
+
+                foreach my $parentDefinition (@parentDefinitions)
+                    {
+                    print CLASS_HIERARCHY_FILEHANDLE pack('N', $definitionIndexes{$parentDefinition});
+                    };
+
+                # [UInt32: 0]
+                print CLASS_HIERARCHY_FILEHANDLE pack('N', 0);
+                };
+            };
+        };
+
+    # [SymbolString: class or undef to end]
+
+    NaturalDocs::SymbolString->ToBinaryFile(\*CLASS_HIERARCHY_FILEHANDLE, undef);
+
+    close(CLASS_HIERARCHY_FILEHANDLE);
+    };
+
+
+#
+#   Function: Purge
+#
+#   Purges the hierarchy of files that no longer have Natural Docs content.
+#
+sub Purge
+    {
+    my ($self) = @_;
+
+    my $filesToPurge = NaturalDocs::Project->FilesToPurge();
+
+    foreach my $file (keys %$filesToPurge)
+        {
+        $self->DeleteFile($file);
+        };
+    };
+
+
+
+###############################################################################
+# Group: Interface Functions
+
+
+#
+#   Function: OnInterpretationChange
+#
+#   Called by <NaturalDocs::SymbolTable> whenever a class hierarchy reference's intepretation changes, meaning it switched
+#   from one symbol to another.
+#
+#       reference - The <ReferenceString> whose current interpretation changed.
+#
+sub OnInterpretationChange #(reference)
+    {
+    my ($self, $reference) = @_;
+
+    if (NaturalDocs::ReferenceString->TypeOf($reference) == ::REFERENCE_CH_PARENT())
+        {
+        # The approach here is simply to completely delete the reference and readd it.  This is less than optimal efficiency, since it's
+        # being removed and added from %files too, even though that isn't required.  However, the simpler code is worth it
+        # considering this will only happen when a parent reference becomes defined or undefined, or on the rare languages (like C#)
+        # that allow relative parent references.
+
+        my $oldTargetSymbol = $parentReferences{$reference};
+        my $oldTargetObject = $classes{$oldTargetSymbol};
+
+        my @classesWithReferenceParent = $oldTargetObject->Children();
+
+        # Each entry is an arrayref of file names.  Indexes are the same as classesWithReferenceParent's.
+        my @filesDefiningReferenceParent;
+
+        foreach my $classWithReferenceParent (@classesWithReferenceParent)
+            {
+            my $fileList = [ $classes{$classWithReferenceParent}->ParentReferenceDefinitions($reference) ];
+            push @filesDefiningReferenceParent, $fileList;
+
+            foreach my $fileDefiningReferenceParent (@$fileList)
+                {
+                $self->DeleteParentReference($fileDefiningReferenceParent, $classWithReferenceParent, $reference);
+                };
+            };
+
+
+        # This will force the reference to be reinterpreted on the next add.
+
+        delete $parentReferences{$reference};
+
+
+        # Now we can just readd it.
+
+        for (my $i = 0; $i < scalar @classesWithReferenceParent; $i++)
+            {
+            foreach my $file (@{$filesDefiningReferenceParent[$i]})
+                {
+                $self->AddParentReference($file, $classesWithReferenceParent[$i], $reference);
+                };
+            };
+        };
+
+    # The only way for a REFERENCE_CH_CLASS reference to change is if the symbol is deleted.  That will be handled by
+    # <AnalyzeChanges()>, so we don't need to do anything here.
+    };
+
+
+#
+#   Function: OnTargetSymbolChange
+#
+#   Called by <NaturalDocs::SymbolTable> whenever a class hierarchy reference's target symbol changes, but the reference
+#   still resolves to the same symbol.
+#
+#   Parameters:
+#
+#       reference - The <ReferenceString> that was affected by the change.
+#
+sub OnTargetSymbolChange #(reference)
+    {
+    my ($self, $reference) = @_;
+
+    my $type = NaturalDocs::ReferenceString->TypeOf($reference);
+    my $class;
+
+    if ($type == ::REFERENCE_CH_PARENT())
+        {  $class = $parentReferences{$reference};  }
+    else # ($type == ::REFERENCE_CH_CLASS())
+        {
+        # Class references are global absolute, so we can just yank the symbol.
+        (undef, $class, undef, undef, undef, undef) = NaturalDocs::ReferenceString->InformationOf($reference);
+        };
+
+    $self->RebuildFilesFor($class, 1, 0, 1);
+    };
+
+
+
+###############################################################################
+# Group: Modification Functions
+
+
+#
+#   Function: AddClass
+#
+#   Adds a class to the hierarchy.
+#
+#   Parameters:
+#
+#       file - The <FileName> the class was defined in.
+#       class - The class <SymbolString>.
+#       languageName - The name of the language this applies to.
+#
+#   Note:
+#
+#       The file parameter must be defined when using this function externally.  It may be undef for internal use only.
+#
+sub AddClass #(file, class, languageName)
+    {
+    my ($self, $file, $class, $languageName) = @_;
+
+    if (!exists $classes{$class})
+        {
+        $classes{$class} = NaturalDocs::ClassHierarchy::Class->New();
+        NaturalDocs::SymbolTable->AddReference($self->ClassReferenceOf($class, $languageName), $file)
+        };
+
+    if (defined $file)
+        {
+        # If this was the first definition for this class...
+        if ($classes{$class}->AddDefinition($file))
+            {  $self->RebuildFilesFor($class, 1, 1, 1);  };
+
+        if (!exists $files{$file})
+            {  $files{$file} = NaturalDocs::ClassHierarchy::File->New();  };
+
+        $files{$file}->AddClass($class);
+
+        if (defined $watchedFileName)
+            {  $watchedFile->AddClass($class);  };
+        };
+    };
+
+
+#
+#   Function: AddParentReference
+#
+#   Adds a class-parent relationship to the hierarchy.  The classes will be created if they don't already exist.
+#
+#   Parameters:
+#
+#       file - The <FileName> the reference was defined in.
+#       class - The class <SymbolString>.
+#       symbol - The parent class <SymbolString>.
+#       scope - The package <SymbolString> that the reference appeared in.
+#       using - An arrayref of package <SymbolStrings> that the reference has access to via "using" statements.
+#       resolvingFlags - Any <Resolving Flags> to be used when resolving the reference.
+#
+#   Alternate Parameters:
+#
+#       file - The <FileName> the reference was defined in.
+#       class - The class <SymbolString>.
+#       reference - The parent <ReferenceString>.
+#
+sub AddParentReference #(file, class, symbol, scope, using, resolvingFlags) or (file, class, reference)
+    {
+    my ($self, $file, $class, $symbol, $parentReference);
+
+    if (scalar @_ == 7)
+        {
+        my ($scope, $using, $resolvingFlags);
+        ($self, $file, $class, $symbol, $scope, $using, $resolvingFlags) = @_;
+
+        $parentReference = NaturalDocs::ReferenceString->MakeFrom(::REFERENCE_CH_PARENT(), $symbol,
+                                                                                                    NaturalDocs::Languages->LanguageOf($file)->Name(),
+                                                                                                    $scope, $using, $resolvingFlags);
+        }
+    else
+        {
+        ($self, $file, $class, $parentReference) = @_;
+        $symbol = (NaturalDocs::ReferenceString->InformationOf($parentReference))[1];
+        };
+
+
+    # In case it doesn't already exist.
+    $self->AddClass($file, $class);
+
+    my $parent;
+    if (exists $parentReferences{$parentReference})
+        {
+        $parent = $parentReferences{$parentReference};
+        }
+    else
+        {
+        NaturalDocs::SymbolTable->AddReference($parentReference, $file);
+        my $parentTarget = NaturalDocs::SymbolTable->References($parentReference);
+
+        if (defined $parentTarget)
+            {  $parent = $parentTarget->Symbol();  }
+        else
+            {  $parent = $symbol;  };
+
+        # In case it doesn't already exist.
+        $self->AddClass(undef, $parent);
+
+        $parentReferences{$parentReference} = $parent;
+        };
+
+
+    # If this defined a new parent...
+    if ($classes{$class}->AddParentReference($parentReference, $file, \%parentReferences))
+        {
+        $classes{$parent}->AddChild($class);
+
+        $self->RebuildFilesFor($class, 0, 1, 0);
+        $self->RebuildFilesFor($parent, 0, 1, 0);
+        };
+
+    $files{$file}->AddParentReference($class, $parentReference);
+
+    if (defined $watchedFileName)
+        {  $watchedFile->AddParentReference($class, $parentReference);  };
+    };
+
+
+#
+#   Function: WatchFileForChanges
+#
+#   Watches a file for changes, which can then be applied by <AnalyzeChanges()>.  Definitions are not deleted via a DeleteClass()
+#   function.  Instead, a file is watched for changes, reparsed, and then a comparison is made to look for definitions that
+#   disappeared and any other relevant changes.
+#
+#   Parameters:
+#
+#       file - The <FileName> to watch.
+#
+sub WatchFileForChanges #(file)
+    {
+    my ($self, $file) = @_;
+
+    $watchedFile = NaturalDocs::ClassHierarchy::File->New();
+    $watchedFileName = $file;
+    };
+
+
+#
+#   Function: AnalyzeChanges
+#
+#   Checks the watched file for any changes that occured since the last time is was parsed, and updates the hierarchy as
+#   necessary.  Also sends any files that are affected to <NaturalDocs::Project->RebuildFile()>.
+#
+sub AnalyzeChanges
+    {
+    my ($self) = @_;
+
+    # If the file didn't have any classes before, and it still doesn't, it wont be in %files.
+    if (exists $files{$watchedFileName})
+        {
+        my @originalClasses = $files{$watchedFileName}->Classes();
+
+        foreach my $originalClass (@originalClasses)
+            {
+            # If the class isn't there the second time around...
+            if (!$watchedFile->HasClass($originalClass))
+                {  $self->DeleteClass($watchedFileName, $originalClass);  }
+
+            else
+                {
+                my @originalParents = $files{$watchedFileName}->ParentReferencesOf($originalClass);
+
+                foreach my $originalParent (@originalParents)
+                    {
+                    # If the parent reference wasn't there the second time around...
+                    if (!$watchedFile->HasParentReference($originalClass, $originalParent))
+                        {  $self->DeleteParentReference($watchedFileName, $originalClass, $originalParent);  };
+                    };
+                };
+            };
+        };
+
+
+    $watchedFile = undef;
+    $watchedFileName = undef;
+    };
+
+
+
+###############################################################################
+# Group: Information Functions
+
+
+#
+#   Function: ParentsOf
+#   Returns a <SymbolString> array of the passed class' parents, or an empty array if none.  Note that not all of them may be
+#   defined.
+#
+sub ParentsOf #(class)
+    {
+    my ($self, $class) = @_;
+
+    if (exists $classes{$class})
+        {  return $classes{$class}->Parents();  }
+    else
+        {  return ( );  };
+    };
+
+#
+#   Function: ChildrenOf
+#   Returns a <SymbolString> array of the passed class' children, or an empty array if none.  Note that not all of them may be
+#   defined.
+#
+sub ChildrenOf #(class)
+    {
+    my ($self, $class) = @_;
+
+    if (exists $classes{$class})
+        {  return $classes{$class}->Children();  }
+    else
+        {  return ( );  };
+    };
+
+
+
+###############################################################################
+# Group: Support Functions
+
+
+#
+#   Function: DeleteFile
+#
+#   Deletes a file and everything defined in it.
+#
+#   Parameters:
+#
+#       file - The <FileName>.
+#
+sub DeleteFile #(file)
+    {
+    my ($self, $file) = @_;
+
+    if (!exists $files{$file})
+        {  return;  };
+
+    my @classes = $files{$file}->Classes();
+    foreach my $class (@classes)
+        {
+        $self->DeleteClass($file, $class);
+        };
+
+    delete $files{$file};
+    };
+
+#
+#   Function: DeleteClass
+#
+#   Deletes a class definition from a file.  Will also delete any parent references from this class and file.  Will rebuild any file
+#   affected unless <dontRebuildFiles> is set.
+#
+#   Parameters:
+#
+#       file - The <FileName> that defines the class.
+#       class - The class <SymbolString>.
+#
+sub DeleteClass #(file, class)
+    {
+    my ($self, $file, $class) = @_;
+
+    my @parents = $files{$file}->ParentReferencesOf($class);
+    foreach my $parent (@parents)
+        {
+        $self->DeleteParentReference($file, $class, $parent);
+        };
+
+    $files{$file}->DeleteClass($class);
+
+    # If we're deleting the last definition of this class.
+    if ($classes{$class}->DeleteDefinition($file))
+        {
+        if (!$classes{$class}->HasChildren())
+            {
+            delete $classes{$class};
+
+            if (!$dontRebuildFiles)
+                {  NaturalDocs::Project->RebuildFile($file);  };
+            }
+        else
+            {  $self->RebuildFilesFor($class, 0, 1, 1);  };
+
+        };
+    };
+
+
+#
+#   Function: DeleteParentReference
+#
+#   Deletes a class' parent reference and returns whether it resulted in the loss of a parent class.  Will rebuild any file affected
+#   unless <dontRebuildFiles> is set.
+#
+#   Parameters:
+#
+#       file - The <FileName> that defines the reference.
+#       class - The class <SymbolString>.
+#       reference - The parent <ReferenceString>.
+#
+#   Returns:
+#
+#       If the class lost a parent as a result of this, it will return its <SymbolString>.  It will return undef otherwise.
+#
+sub DeleteParentReference #(file, class, reference)
+    {
+    my ($self, $file, $class, $reference) = @_;
+
+    if (!exists $classes{$class})
+        {  return;  };
+
+    $files{$file}->DeleteParentReference($class, $reference);
+
+    my $deletedParent = $classes{$class}->DeleteParentReference($reference, $file, \%parentReferences);
+
+    if (defined $deletedParent)
+        {
+        my $deletedParentObject = $classes{$deletedParent};
+
+        $deletedParentObject->DeleteChild($class);
+
+        $self->RebuildFilesFor($deletedParent, 0, 1, 0);
+        $self->RebuildFilesFor($class, 0, 1, 0);
+
+        if (!$deletedParentObject->HasChildren() && !$deletedParentObject->IsDefined())
+            {
+            delete $classes{$deletedParent};
+            NaturalDocs::SymbolTable->DeleteReference(
+                $self->ClassReferenceOf($class, NaturalDocs::Languages->LanguageOf($file)->Name()) );
+            };
+
+        return $deletedParent;
+        };
+
+    return undef;
+    };
+
+
+#
+#   Function: ClassReferenceOf
+#
+#   Returns the <REFERENCE_CH_CLASS> <ReferenceString> of the passed class <SymbolString>.
+#
+sub ClassReferenceOf #(class, languageName)
+    {
+    my ($self, $class, $languageName) = @_;
+
+    return NaturalDocs::ReferenceString->MakeFrom(::REFERENCE_CH_CLASS(), $class, $languageName, undef, undef,
+                                                                            ::RESOLVE_ABSOLUTE() | ::RESOLVE_NOPLURAL());
+    };
+
+
+#
+#   Function: RebuildFilesFor
+#
+#   Calls <NaturalDocs::Project->RebuildFile()> for every file defining the passed class, its parents, and/or its children.
+#   Returns without doing anything if <dontRebuildFiles> is set.
+#
+#   Parameters:
+#
+#       class - The class <SymbolString>.
+#       rebuildParents - Whether to rebuild the class' parents.
+#       rebuildSelf - Whether to rebuild the class.
+#       rebuildChildren - Whether to rebuild the class' children.
+#
+sub RebuildFilesFor #(class, rebuildParents, rebuildSelf, rebuildChildren)
+    {
+    my ($self, $class, $rebuildParents, $rebuildSelf, $rebuildChildren) = @_;
+
+    if ($dontRebuildFiles)
+        {  return;  };
+
+    my @classesToBuild;
+
+    if ($rebuildParents)
+        {  @classesToBuild = $classes{$class}->Parents();  };
+    if ($rebuildSelf)
+        {  push @classesToBuild, $class;  };
+    if ($rebuildChildren)
+        {  push @classesToBuild, $classes{$class}->Children();  };
+
+    foreach my $classToBuild (@classesToBuild)
+        {
+        my @definitions = $classes{$classToBuild}->Definitions();
+
+        foreach my $definition (@definitions)
+            {  NaturalDocs::Project->RebuildFile($definition);  };
+        };
+    };
+
+
+1;

Added: trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/ConfigFile.pm
===================================================================
--- trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/ConfigFile.pm	                        (rev 0)
+++ trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/ConfigFile.pm	2009-07-01 15:30:00 UTC (rev 4275)
@@ -0,0 +1,497 @@
+###############################################################################
+#
+#   Package: NaturalDocs::ConfigFile
+#
+###############################################################################
+#
+#   A package to manage Natural Docs' configuration files.
+#
+#   Usage:
+#
+#       - Only one configuration file can be managed with this package at a time.  You must close the file before opening another
+#         one.
+#
+###############################################################################
+
+# 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::ConfigFile;
+
+
+
+#
+#   Topic: Format
+#
+#   All configuration files are text files.
+#
+#   > # [comment]
+#
+#   Comments start with the # character.
+#
+#   > Format: [version]
+#
+#   All configuration files *must* have a format line as its first line containing content.  Whitespace and comments are permitted
+#   ahead of it.
+#
+#   > [keyword]: [value]
+#
+#   Keywords can only contain <CFChars>.  Keywords are not case sensitive.  Values can be anything and run until the end of
+#   the line or a comment.
+#
+#   > [value]
+#
+#   Lines that don't start with a valid keyword format are considered to be all value.
+#
+#   > [line] { [line] } [line]
+#
+#   Files supporting brace groups (specified in <Open()>) may also have braces that can appear anywhere.  It allows more than
+#   one thing to appear per line, which isn't supported otherwise.  Consequently, values may not have braces.
+#
+
+
+#
+#   Type: CFChars
+#
+#   The characters that can appear in configuration file keywords and user-defined element names: letters, numbers, spaces,
+#   dashes, slashes, apostrophes, and periods.
+#
+#   Although the list above is exhaustive, it should be noted that you especially can *not* use colons (messes up keyword: value
+#   sequences) commas (messes up item, item, item list sequences) and hashes (messes up comment detection.)
+#
+#   You can search the source code for [CFChars] to find all the instances where this definition is used.
+#
+
+
+###############################################################################
+# Group: Variables
+
+#
+#   handle: CONFIG_FILEHANDLE
+#
+#   The file handle used for the configuration file.
+#
+
+
+#
+#   string: file
+#
+#   The <FileName> for the current configuration file being parsed.
+#
+my $file;
+
+
+#
+#   array: errors
+#
+#   An array of errors added by <AddError()>.  Every odd entry is the line number, and every even entry following is the
+#   error message.
+#
+my @errors;
+
+
+#
+#   var: lineNumber
+#
+#   The current line number for the configuration file.
+#
+my $lineNumber;
+
+
+#
+#   bool: hasBraceGroups
+#
+#   Whether the file has brace groups or not.
+#
+my $hasBraceGroups;
+
+
+#
+#   array: virtualLines
+#
+#   An array of virtual lines if a line from the file contained more than one.
+#
+#   Files with brace groups may have more than one virtual line per actual file line, such as "Group: A { Group: B".  When that
+#   happens, any extra virtual lines are put into here so they can be returned on the next call.
+#
+my @virtualLines;
+
+
+
+###############################################################################
+# Group: Functions
+
+
+#
+#   Function: Open
+#
+#   Opens a configuration file for parsing and returns the format <VersionInt>.
+#
+#   Parameters:
+#
+#       file - The <FileName> to parse.
+#       hasBraceGroups - Whether the file supports brace groups or not.  If so, lines with braces will be split apart behind the
+#                                  scenes.
+#
+#   Returns:
+#
+#       The <VersionInt> of the file, or undef if the file doesn't exist.
+#
+sub Open #(file, hasBraceGroups)
+    {
+    my $self;
+    ($self, $file, $hasBraceGroups) = @_;
+
+    @errors = ( );
+
+    # It will be incremented to one when the first line is read from the file.
+    $lineNumber = 0;
+
+    open(CONFIG_FILEHANDLE, '<' . $file) or return undef;
+
+
+    # Get the format line.
+
+    my ($keyword, $value, $comment) = $self->GetLine();
+
+    if ($keyword eq 'format')
+        {  return NaturalDocs::Version->FromString($value);  }
+    else
+        {  die "The first content line in " . $file . " must be the Format: line.\n";  };
+    };
+
+
+#
+#   Function: Close
+#
+#   Closes the current configuration file.
+#
+sub Close
+    {
+    my $self = shift;
+    close(CONFIG_FILEHANDLE);
+    };
+
+
+#
+#   Function: GetLine
+#
+#   Returns the next line containing content, or an empty array if none.
+#
+#   Returns:
+#
+#       Returns the array ( keyword, value, comment ), or an empty array if none.  All tabs will be converted to spaces, and all
+#       whitespace will be condensed into a single space.
+#
+#       keyword - The keyword part of the line, if any.  Is converted to lowercase and doesn't include the colon.  If the file supports
+#                       brace groups, opening and closing braces will be returned as keywords.
+#       value - The value part of the line, minus any whitespace.  Keeps its original case.
+#       comment - The comment following the line, if any.  This includes the # symbol and a leading space if there was
+#                       any whitespace, since it may be significant.  Otherwise undef.  Used for lines where the # character needs to be
+#                       accepted as part of the value.
+#
+sub GetLine
+    {
+    my $self = shift;
+
+    my ($line, $comment);
+
+
+    # Get the next line with content.
+
+    do
+        {
+        # Get the next line.
+
+        my $isFileLine;
+
+        if (scalar @virtualLines)
+            {
+            $line = shift @virtualLines;
+            $isFileLine = 0;
+            }
+        else
+            {
+            $line = <CONFIG_FILEHANDLE>;
+            $lineNumber++;
+
+            if (!defined $line)
+                {  return ( );  };
+
+            ::XChomp(\$line);
+
+            # Condense spaces and tabs into a single space.
+            $line =~ tr/\t /  /s;
+            $isFileLine = 1;
+            };
+
+
+        # Split off the comment.
+
+        if ($line =~ /^(.*?)( ?#.*)$/)
+            {  ($line, $comment) = ($1, $2);  }
+        else
+            {  $comment = undef;  };
+
+
+        # Split any brace groups.
+
+        if ($isFileLine && $hasBraceGroups && $line =~ /[\{\}]/)
+            {
+            ($line, @virtualLines) = split(/([\{\}])/, $line);
+
+            $virtualLines[-1] .= $comment;
+            $comment = undef;
+            };
+
+
+        # Remove whitespace.
+
+        $line =~ s/^ //;
+        $line =~ s/ $//;
+        $comment =~ s/ $//;
+        # We want to keep the leading space on a comment.
+        }
+    while (!$line);
+
+
+    # Process the line.
+
+    if ($hasBraceGroups && ($line eq '{' || $line eq '}'))
+        {
+        return ($line, undef, undef);
+        };
+
+
+    if ($line =~ /^([a-z0-9\ \'\/\.\-]+?) ?: ?(.*)$/i) # [CFChars]
+        {
+        my ($keyword, $value) = ($1, $2);
+        return (lc($keyword), $value, $comment);
+        }
+
+    else
+        {
+        return (undef, $line, $comment);
+        };
+    };
+
+
+#
+#   Function: LineNumber
+#
+#   Returns the line number for the line last returned by <GetLine()>.
+#
+sub LineNumber
+    {  return $lineNumber;  };
+
+
+
+###############################################################################
+# Group: Error Functions
+
+
+#
+#   Function: AddError
+#
+#   Stores an error for the current configuration file.  Will be attached to the last line read by <GetLine()>.
+#
+#   Parameters:
+#
+#       message - The error message.
+#       lineNumber - The line number to use.  If not specified, it will use the line number from the last call to <GetLine()>.
+#
+sub AddError #(message, lineNumber)
+    {
+    my ($self, $message, $messageLineNumber) = @_;
+
+    if (!defined $messageLineNumber)
+        {  $messageLineNumber = $lineNumber;  };
+
+    push @errors, $messageLineNumber, $message;
+    };
+
+
+#
+#   Function: ErrorCount
+#
+#   Returns how many errors the configuration file has.
+#
+sub ErrorCount
+    {
+    return (scalar @errors) / 2;
+    };
+
+
+#
+#   Function: PrintErrorsAndAnnotateFile
+#
+#   Prints the errors to STDERR in the standard GNU format and annotates the configuration file with them.  It does *not* end
+#   execution.  <Close()> *must* be called before this function.
+#
+sub PrintErrorsAndAnnotateFile
+    {
+    my ($self) = @_;
+
+    if (scalar @errors)
+        {
+        open(CONFIG_FILEHANDLE, '<' . $file);
+        my @lines = <CONFIG_FILEHANDLE>;
+        close(CONFIG_FILEHANDLE);
+
+        # We need to keep track of both the real and the original line numbers.  The original line numbers are for matching errors in
+        # the errors array, and don't include any comment lines added or deleted.  Line number is the current line number including
+        # those comment lines for sending to the display.
+        my $lineNumber = 1;
+        my $originalLineNumber = 1;
+
+        open(CONFIG_FILEHANDLE, '>' . $file);
+
+        # We don't want to keep the old error header, if present.
+        if ($lines[0] =~ /^\# There (?:is an error|are \d+ errors) in this file\./)
+            {
+            shift @lines;
+            $originalLineNumber++;
+
+            # We want to drop the blank line after it as well.
+            if ($lines[0] eq "\n")
+                {
+                shift @lines;
+                $originalLineNumber++;
+                };
+            };
+
+        if ($self->ErrorCount() == 1)
+            {
+            print CONFIG_FILEHANDLE
+            "# There is an error in this file.  Search for ERROR to find it.\n\n";
+            }
+        else
+            {
+            print CONFIG_FILEHANDLE
+            "# There are " . $self->ErrorCount() . " errors in this file.  Search for ERROR to find them.\n\n";
+            };
+
+        $lineNumber += 2;
+
+
+        foreach my $line (@lines)
+            {
+            while (scalar @errors && $originalLineNumber == $errors[0])
+                {
+                my $errorLine = shift @errors;
+                my $errorMessage = shift @errors;
+
+                print CONFIG_FILEHANDLE "# ERROR: " . $errorMessage . "\n";
+
+                # Use the GNU error format, which should make it easier to handle errors when Natural Docs is part of a build process.
+                # See http://www.gnu.org/prep/standards_15.html
+
+                $errorMessage = lcfirst($errorMessage);
+                $errorMessage =~ s/\.$//;
+
+                print STDERR 'NaturalDocs:' . $file . ':' . $lineNumber . ': ' . $errorMessage . "\n";
+
+                $lineNumber++;
+                };
+
+            # We want to remove error lines from previous runs.
+            if (substr($line, 0, 9) ne '# ERROR: ')
+                {
+                print CONFIG_FILEHANDLE $line;
+                $lineNumber++;
+                };
+
+            $originalLineNumber++;
+            };
+
+        # Clean up any remaining errors.
+        while (scalar @errors)
+            {
+            my $errorLine = shift @errors;
+            my $errorMessage = shift @errors;
+
+            print CONFIG_FILEHANDLE "# ERROR: " . $errorMessage . "\n";
+
+            # Use the GNU error format, which should make it easier to handle errors when Natural Docs is part of a build process.
+            # See http://www.gnu.org/prep/standards_15.html
+
+            $errorMessage = lcfirst($errorMessage);
+            $errorMessage =~ s/\.$//;
+
+            print STDERR 'NaturalDocs:' . $file . ':' . $lineNumber . ': ' . $errorMessage . "\n";
+            };
+
+        close(CONFIG_FILEHANDLE);
+        };
+    };
+
+
+
+###############################################################################
+# Group: Misc Functions
+
+
+#
+#   Function: HasOnlyCFChars
+#
+#   Returns whether the passed string contains only <CFChars>.
+#
+sub HasOnlyCFChars #(string)
+    {
+    my ($self, $string) = @_;
+    return ($string =~ /^[a-z0-9\ \.\-\/\']*$/i);  # [CFChars]
+    };
+
+
+#
+#   Function: CFCharNames
+#
+#   Returns a plain-english list of <CFChars> which can be embedded in a sentence.  For example, "You can only use
+#   [CFCharsList()] in the name.
+#
+sub CFCharNames
+    {
+    # [CFChars]
+    return 'letters, numbers, spaces, periods, dashes, slashes, and apostrophes';
+    };
+
+
+#
+#   Function: Obscure
+#
+#   Obscures the passed text so that it is not user editable and returns it.  The encoding method is not secure; it is just designed
+#   to be fast and to discourage user editing.
+#
+sub Obscure #(text)
+    {
+    my ($self, $text) = @_;
+
+    # ` is specifically chosen to encode to space because of its rarity.  We don't want a trailing one to get cut off before decoding.
+    $text =~ tr{a-zA-Z0-9\ \\\/\.\:\_\-\`}
+                    {pY9fGc\`R8lAoE\\uIdH6tN\/7sQjKx0B5mW\.vZ41PyFg\:CrLaO\_eUi2DhT\-nSqJkXb3MwVz\ };
+
+    return $text;
+    };
+
+
+#
+#   Function: Unobscure
+#
+#   Restores text encoded with <Obscure()> and returns it.
+#
+sub Unobscure #(text)
+    {
+    my ($self, $text) = @_;
+
+    $text =~ tr{pY9fGc\`R8lAoE\\uIdH6tN\/7sQjKx0B5mW\.vZ41PyFg\:CrLaO\_eUi2DhT\-nSqJkXb3MwVz\ }
+                    {a-zA-Z0-9\ \\\/\.\:\_\-\`};
+
+    return $text;
+    };
+
+
+
+1;

Added: trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Constants.pm
===================================================================
--- trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Constants.pm	                        (rev 0)
+++ trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Constants.pm	2009-07-01 15:30:00 UTC (rev 4275)
@@ -0,0 +1,165 @@
+###############################################################################
+#
+#   Package: NaturalDocs::Constants
+#
+###############################################################################
+#
+#   Constants that are used throughout the script.  All are exported by default.
+#
+###############################################################################
+
+# 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::Constants;
+
+use vars qw(@EXPORT @ISA);
+require Exporter;
+ at ISA = qw(Exporter);
+
+ at EXPORT = ('MENU_TITLE', 'MENU_SUBTITLE', 'MENU_FILE', 'MENU_GROUP', 'MENU_TEXT', 'MENU_LINK', 'MENU_FOOTER',
+                   'MENU_INDEX', 'MENU_FORMAT', 'MENU_ENDOFORIGINAL', 'MENU_DATA',
+
+                   'MENU_FILE_NOAUTOTITLE', 'MENU_GROUP_UPDATETITLES', 'MENU_GROUP_UPDATESTRUCTURE',
+                   'MENU_GROUP_UPDATEORDER', 'MENU_GROUP_HASENDOFORIGINAL',
+                   'MENU_GROUP_UNSORTED', 'MENU_GROUP_FILESSORTED',
+                   'MENU_GROUP_FILESANDGROUPSSORTED', 'MENU_GROUP_EVERYTHINGSORTED',
+                   'MENU_GROUP_ISINDEXGROUP',
+
+                   'FILE_NEW', 'FILE_CHANGED', 'FILE_SAME', 'FILE_DOESNTEXIST');
+
+#
+#   Topic: Assumptions
+#
+#   - No constant here will ever be zero.
+#   - All constants are exported by default.
+#
+
+
+###############################################################################
+# Group: Virtual Types
+# These are only groups of constants, but should be treated like typedefs or enums.  Each one represents a distinct type and
+# their values should only be one of their constants or undef.
+
+
+#
+#   Constants: MenuEntryType
+#
+#   The types of entries that can appear in the menu.
+#
+#       MENU_TITLE         - The title of the menu.
+#       MENU_SUBTITLE   - The sub-title of the menu.
+#       MENU_FILE           - A source file, relative to the source directory.
+#       MENU_GROUP       - A group.
+#       MENU_TEXT          - Arbitrary text.
+#       MENU_LINK           - A web link.
+#       MENU_FOOTER      - Footer text.
+#       MENU_INDEX        - An index.
+#       MENU_FORMAT     - The version of Natural Docs the menu file was generated with.
+#       MENU_ENDOFORIGINAL - A dummy entry that marks where the original group content ends.  This is used when automatically
+#                                           changing the groups so that the alphabetization or lack thereof can be detected without being
+#                                           affected by new entries tacked on to the end.
+#       MENU_DATA - Data not meant for user editing.
+#
+#   Dependency:
+#
+#       <PreviousMenuState.nd> depends on these values all being able to fit into a UInt8, i.e. <= 255.
+#
+use constant MENU_TITLE => 1;
+use constant MENU_SUBTITLE => 2;
+use constant MENU_FILE => 3;
+use constant MENU_GROUP => 4;
+use constant MENU_TEXT => 5;
+use constant MENU_LINK => 6;
+use constant MENU_FOOTER => 7;
+use constant MENU_INDEX => 8;
+use constant MENU_FORMAT => 9;
+use constant MENU_ENDOFORIGINAL => 10;
+use constant MENU_DATA => 11;
+
+
+#
+#   Constants: FileStatus
+#
+#   What happened to a file since Natural Docs' last execution.
+#
+#       FILE_NEW                - The file has been added since the last run.
+#       FILE_CHANGED        - The file has been modified since the last run.
+#       FILE_SAME               - The file hasn't been modified since the last run.
+#       FILE_DOESNTEXIST  - The file doesn't exist, or was deleted.
+#
+use constant FILE_NEW => 1;
+use constant FILE_CHANGED => 2;
+use constant FILE_SAME => 3;
+use constant FILE_DOESNTEXIST => 4;
+
+
+
+###############################################################################
+# Group: Flags
+# These constants can be combined with each other.
+
+
+#
+#   Constants: Menu Entry Flags
+#
+#   The various flags that can apply to a menu entry.  You cannot mix flags of different types, since they may overlap.
+#
+#   File Flags:
+#
+#       MENU_FILE_NOAUTOTITLE - Whether the file is auto-titled or not.
+#
+#   Group Flags:
+#
+#       MENU_GROUP_UPDATETITLES - The group should have its auto-titles regenerated.
+#       MENU_GROUP_UPDATESTRUCTURE - The group should be checked for structural changes, such as being removed or being
+#                                                             split into subgroups.
+#       MENU_GROUP_UPDATEORDER - The group should be resorted.
+#
+#       MENU_GROUP_HASENDOFORIGINAL - Whether the group contains a dummy <MENU_ENDOFORIGINAL> entry.
+#       MENU_GROUP_ISINDEXGROUP - Whether the group is used primarily for <MENU_INDEX> entries.  <MENU_TEXT> entries
+#                                                       are tolerated.
+#
+#       MENU_GROUP_UNSORTED - The group's contents are not sorted.
+#       MENU_GROUP_FILESSORTED - The group's files are sorted alphabetically.
+#       MENU_GROUP_FILESANDGROUPSSORTED - The group's files and sub-groups are sorted alphabetically.
+#       MENU_GROUP_EVERYTHINGSORTED - All entries in the group are sorted alphabetically.
+#
+use constant MENU_FILE_NOAUTOTITLE => 0x0001;
+
+use constant MENU_GROUP_UPDATETITLES => 0x0001;
+use constant MENU_GROUP_UPDATESTRUCTURE => 0x0002;
+use constant MENU_GROUP_UPDATEORDER => 0x0004;
+use constant MENU_GROUP_HASENDOFORIGINAL => 0x0008;
+
+# This could really be a two-bit field instead of four flags, but it's not worth the effort since it's only used internally.
+use constant MENU_GROUP_UNSORTED => 0x0010;
+use constant MENU_GROUP_FILESSORTED => 0x0020;
+use constant MENU_GROUP_FILESANDGROUPSSORTED => 0x0040;
+use constant MENU_GROUP_EVERYTHINGSORTED => 0x0080;
+
+use constant MENU_GROUP_ISINDEXGROUP => 0x0100;
+
+
+
+
+###############################################################################
+# Group: Support Functions
+
+
+#
+#   Function: IsClassHierarchyReference
+#   Returns whether the passed <ReferenceType> belongs to <NaturalDocs::ClassHierarchy>.
+#
+sub IsClassHierarchyReference #(reference)
+    {
+    my ($self, $reference) = @_;
+    return ($reference == ::REFERENCE_CH_CLASS() || $reference == ::REFERENCE_CH_PARENT());
+    };
+
+
+
+1;

Added: trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/DefineMembers.pm
===================================================================
--- trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/DefineMembers.pm	                        (rev 0)
+++ trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/DefineMembers.pm	2009-07-01 15:30:00 UTC (rev 4275)
@@ -0,0 +1,100 @@
+###############################################################################
+#
+#   Package: NaturalDocs::DefineMembers
+#
+###############################################################################
+#
+#   A custom Perl pragma to define member constants and accessors for use in Natural Docs objects while supporting inheritance.
+#
+#   Each member will be defined as a numeric constant which should be used as that variable's index into the object arrayref.
+#   They will be assigned sequentially from zero, and take into account any members defined this way in parent classes.  Note
+#   that you can *not* use multiple inheritance with this method.
+#
+#   If a parameter ends in parenthesis, it will be generated as an accessor for the previous member.  If it also starts with "Set",
+#   the accessor will accept a single parameter to replace the value with.  If it's followed with "duparrayref", it will assume the
+#   parameter is either an arrayref or undef, and if the former, will duplicate it to set the value.
+#
+#   Example:
+#
+#   > package MyPackage;
+#   >
+#   > use NaturalDocs::DefineMembers 'VAR_A', 'VarA()', 'SetVarA()',
+#   >                                'VAR_B', 'VarB()',
+#   >                                'VAR_C',
+#   >                                'VAR_D', 'VarD()', 'SetVarD() duparrayref';
+#   >
+#   > sub SetC #(C)
+#   >    {
+#   >    my ($self, $c) = @_;
+#   >    $self->[VAR_C] = $c;
+#   >    };
+#
+###############################################################################
+
+# This file is part of Natural Docs, which is Copyright (C) 2003-2008 Greg Valure
+# Natural Docs is licensed under the GPL
+
+
+package NaturalDocs::DefineMembers;
+
+sub import #(member, member, ...)
+    {
+    my ($self, @parameters) = @_;
+    my $package = caller();
+
+    no strict 'refs';
+    my $parent = ${$package . '::ISA'}[0];
+    use strict 'refs';
+
+    my $memberConstant = 0;
+    my $lastMemberName;
+
+    if (defined $parent && $parent->can('END_OF_MEMBERS'))
+        {  $memberConstant = $parent->END_OF_MEMBERS();  };
+
+    my $code = '{ package ' . $package . ";\n";
+
+    foreach my $parameter (@parameters)
+        {
+        if ($parameter =~ /^(.+)\(\) *(duparrayref)?$/i)
+            {
+            my ($functionName, $pragma) = ($1, lc($2));
+
+            if ($functionName =~ /^Set/)
+                {
+                if ($pragma eq 'duparrayref')
+                    {
+                    $code .=
+                    'sub ' . $functionName . '
+                        {
+                        if (defined $_[1])
+                            {  $_[0]->[' . $lastMemberName . '] = [ @{$_[1]} ];  }
+                        else
+                            {  $_[0]->[' . $lastMemberName . '] = undef;  };
+                        };' . "\n";
+                    }
+                else
+                    {
+                    $code .= 'sub ' . $functionName . ' { $_[0]->[' . $lastMemberName . '] = $_[1];  };' . "\n";
+                    };
+                }
+            else
+                {
+                $code .= 'sub ' . $functionName . ' { return $_[0]->[' . $lastMemberName . '];  };' . "\n";
+                };
+            }
+        else
+            {
+            $code .= 'use constant ' . $parameter . ' => ' . $memberConstant . ";\n";
+            $memberConstant++;
+            $lastMemberName = $parameter;
+            };
+        };
+
+    $code .= 'use constant END_OF_MEMBERS => ' . $memberConstant . ";\n";
+    $code .= '};';
+
+    eval $code;
+    };
+
+1;

Added: trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Error.pm
===================================================================
--- trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Error.pm	                        (rev 0)
+++ trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Error.pm	2009-07-01 15:30:00 UTC (rev 4275)
@@ -0,0 +1,305 @@
+###############################################################################
+#
+#   Package: NaturalDocs::Error
+#
+###############################################################################
+#
+#   Manages all aspects of error handling in Natural Docs.
+#
+###############################################################################
+
+# 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;
+
+$SIG{'__DIE__'} = \&NaturalDocs::Error::CatchDeath;
+
+
+package NaturalDocs::Error;
+
+
+###############################################################################
+# Group: Variables
+
+
+#
+#   handle: FH_CRASHREPORT
+#   The filehandle used for generating crash reports.
+#
+
+
+#
+#   var: stackTrace
+#   The stack trace generated by <CatchDeath()>.
+#
+my $stackTrace;
+
+
+#
+#   var: softDeath
+#   Whether the program exited using <SoftDeath()>.
+#
+my $softDeath;
+
+
+#
+#   var: currentAction
+#   What Natural Docs was doing when it crashed.  This stores strings generated by functions like <OnStartParsing()>.
+#
+my $currentAction;
+
+
+###############################################################################
+# Group: Functions
+
+
+#
+#   Function: SoftDeath
+#
+#   Generates a "soft" death, which means the program exits like with Perl's die(), but no crash report will be generated.
+#
+#   Parameter:
+#
+#       message - The error message to die with.
+#
+sub SoftDeath #(message)
+    {
+    my ($self, $message) = @_;
+
+    $softDeath = 1;
+    if ($message !~ /\n$/)
+        {  $message .= "\n";  };
+
+    die $message;
+    };
+
+
+#
+#   Function: OnStartParsing
+#
+#   Called whenever <NaturalDocs::Parser> starts parsing a source file.
+#
+sub OnStartParsing #(FileName file)
+    {
+    my ($self, $file) = @_;
+    $currentAction = 'Parsing ' . $file;
+    };
+
+
+#
+#   Function: OnEndParsing
+#
+#   Called whenever <NaturalDocs::Parser> is done parsing a source file.
+#
+sub OnEndParsing #(FileName file)
+    {
+    my ($self, $file) = @_;
+    $currentAction = undef;
+    };
+
+
+#
+#   Function: OnStartBuilding
+#
+#   Called whenever <NaturalDocs::Builder> starts building a source file.
+#
+sub OnStartBuilding #(FileName file)
+    {
+    my ($self, $file) = @_;
+    $currentAction = 'Building ' . $file;
+    };
+
+
+#
+#   Function: OnEndBuilding
+#
+#   Called whenever <NaturalDocs::Builder> is done building a source file.
+#
+sub OnEndBuilding #(FileName file)
+    {
+    my ($self, $file) = @_;
+    $currentAction = undef;
+    };
+
+
+#
+#   Function: HandleDeath
+#
+#   Should be called whenever Natural Docs dies out of execution.
+#
+sub HandleDeath
+    {
+    my $self = shift;
+
+    my $reason = $::EVAL_ERROR;
+    $reason =~ s/[\n\r]+$//;
+
+    my $errorMessage =
+         "\n"
+         . "Natural Docs encountered the following error and was stopped:\n"
+         . "\n"
+         . "   " . $reason . "\n"
+         . "\n"
+
+         . "You can get help at the following web site:\n"
+         . "\n"
+         . "   " . NaturalDocs::Settings->AppURL() . "\n"
+         . "\n";
+
+    if (!$softDeath)
+        {
+        my $crashReport = $self->GenerateCrashReport();
+
+        if ($crashReport)
+            {
+            $errorMessage .=
+             "If sending an error report, please include the information found in the\n"
+             . "following file:\n"
+             . "\n"
+             . "   " . $crashReport . "\n"
+             . "\n";
+            }
+        else
+            {
+            $errorMessage .=
+             "If sending an error report, please include the following information:\n"
+             . "\n"
+             . "   Natural Docs version: " . NaturalDocs::Settings->TextAppVersion() . "\n"
+             . "   Perl version: " . $self->PerlVersion() . " on " . $::OSNAME . "\n"
+             . "\n";
+             };
+        };
+
+    die $errorMessage;
+    };
+
+
+###############################################################################
+# Group: Support Functions
+
+
+#
+#   Function: PerlVersion
+#   Returns the current Perl version as a string.
+#
+sub PerlVersion
+    {
+    my $self = shift;
+
+    my $perlVersion;
+
+    if ($^V)
+        {  $perlVersion = sprintf('%vd', $^V);  }
+    if (!$perlVersion || substr($perlVersion, 0, 1) eq '%')
+        {  $perlVersion = $];  };
+
+    return $perlVersion;
+    };
+
+
+#
+#   Function: GenerateCrashReport
+#
+#   Generates a report and returns the <FileName> it's located at.  Returns undef if it could not generate one.
+#
+sub GenerateCrashReport
+    {
+    my $self = shift;
+
+    my $errorMessage = $::EVAL_ERROR;
+    $errorMessage =~ s/[\r\n]+$//;
+
+    my $reportDirectory = NaturalDocs::Settings->ProjectDirectory();
+
+    if (!$reportDirectory || !-d $reportDirectory)
+        {  return undef;  };
+
+    my $file = NaturalDocs::File->JoinPaths($reportDirectory, 'LastCrash.txt');
+
+    open(FH_CRASHREPORT, '>' . $file) or return undef;
+
+    print FH_CRASHREPORT
+    'Crash Message:' . "\n\n"
+    . '   ' . $errorMessage . "\n\n";
+
+    if ($currentAction)
+        {
+        print FH_CRASHREPORT
+        'Current Action:' . "\n\n"
+        . '   ' . $currentAction . "\n\n";
+        };
+
+    print FH_CRASHREPORT
+    'Natural Docs version ' . NaturalDocs::Settings->TextAppVersion() . "\n"
+    . 'Perl version ' . $self->PerlVersion . ' on ' . $::OSNAME . "\n\n"
+    . 'Command Line:' . "\n\n"
+    . '   ' . join(' ', @ARGV) . "\n\n";
+
+    if ($stackTrace)
+        {
+        print FH_CRASHREPORT
+        'Stack Trace:' . "\n\n"
+        . $stackTrace;
+        }
+    else
+        {
+        print FH_CRASHREPORT
+        'Stack Trace not available.' . "\n\n";
+        };
+
+    close(FH_CRASHREPORT);
+    return $file;
+    };
+
+
+###############################################################################
+# Group: Signal Handlers
+
+
+#
+#   Function: CatchDeath
+#
+#   Catches Perl die calls.
+#
+#   *IMPORTANT:* This function is a signal handler and should not be called manually.  Also, because of this, it does not have
+#   a $self parameter.
+#
+#   Parameters:
+#
+#       message - The error message to die with.
+#
+sub CatchDeath #(message)
+    {
+    # No $self because it's a signal handler.
+    my $message = shift;
+
+    if (!$NaturalDocs::Error::softDeath)
+        {
+        my $i = 0;
+        my ($lastPackage, $lastFile, $lastLine, $lastFunction);
+
+        while (my ($package, $file, $line, $function) = caller($i))
+            {
+            if ($i != 0)
+                {  $stackTrace .= ', called from' . "\n";  };
+
+            $stackTrace .= '   ' . $function;
+
+            if (defined $lastLine)
+                {
+                $stackTrace .= ', line ' . $lastLine;
+
+                if ($function !~ /^NaturalDocs::/)
+                    {  $stackTrace .= ' of ' . $lastFile;  };
+                };
+
+            ($lastPackage, $lastFile, $lastLine, $lastFunction) = ($package, $file, $line, $function);
+            $i++;
+            };
+        };
+    };
+
+
+1;

Added: trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/File.pm
===================================================================
--- trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/File.pm	                        (rev 0)
+++ trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/File.pm	2009-07-01 15:30:00 UTC (rev 4275)
@@ -0,0 +1,540 @@
+###############################################################################
+#
+#   Package: NaturalDocs::File
+#
+###############################################################################
+#
+#   A package to manage file access across platforms.  Incorporates functions from various standard File:: packages, but more
+#   importantly, works around the glorious suckage present in File::Spec, at least in version 0.82 and earlier.  Read the "Why oh
+#   why?" sections for why this package was necessary.
+#
+#   Usage and Dependencies:
+#
+#       - The package doesn't depend on any other Natural Docs packages and is ready to use immediately.
+#
+#       - All functions except <CanonizePath()> assume that all parameters are canonized.
+#
+###############################################################################
+
+# This file is part of Natural Docs, which is Copyright (C) 2003-2008 Greg Valure
+# Natural Docs is licensed under the GPL
+
+use File::Spec ();
+use File::Path ();
+use File::Copy ();
+
+use strict;
+use integer;
+
+package NaturalDocs::File;
+
+
+#
+#   Function: CheckCompatibility
+#
+#   Checks if the standard packages required by this one are up to snuff and dies if they aren't.  This is done because I can't
+#   tell which versions of File::Spec have splitpath just by the version numbers.
+#
+sub CheckCompatibility
+    {
+    my ($self) = @_;
+
+    eval {
+        File::Spec->splitpath('');
+    };
+
+    if ($@)
+        {
+        NaturalDocs::Error->SoftDeath("Natural Docs requires a newer version of File::Spec than you have.  "
+                                                    . "You must either upgrade it or upgrade Perl.");
+        };
+    };
+
+
+###############################################################################
+# Group: Path String Functions
+
+
+#
+#   Function: CanonizePath
+#
+#   Takes a path and returns a logically simplified version of it.
+#
+#   Why oh why?:
+#
+#       Because File::Spec->canonpath doesn't strip quotes on Windows.  So if you pass in "a b\c" or "a b"\c, they still end up as
+#       different strings even though they're logically the same.
+#
+#       It also doesn't remove things like "..", so "a/b/../c" doesn't simplify to "a/c" like it should.
+#
+sub CanonizePath #(path)
+    {
+    my ($self, $path) = @_;
+
+    if ($::OSNAME eq 'MSWin32')
+        {
+        # We don't have to use a smarter algorithm for dropping quotes because they're invalid characters for actual file and
+        # directory names.
+        $path =~ s/\"//g;
+        };
+
+    $path = File::Spec->canonpath($path);
+
+    # Condense a/b/../c into a/c.
+
+    my $upDir = File::Spec->updir();
+    if (index($path, $upDir) != -1)
+        {
+        my ($volume, $directoryString, $file) = $self->SplitPath($path);
+        my @directories = $self->SplitDirectories($directoryString);
+
+        my $i = 1;
+        while ($i < scalar @directories)
+            {
+            if ($i > 0 && $directories[$i] eq $upDir && $directories[$i - 1] ne $upDir)
+                {
+                splice(@directories, $i - 1, 2);
+                $i--;
+                }
+            else
+                {  $i++;  };
+            };
+
+        $directoryString = $self->JoinDirectories(@directories);
+        $path = $self->JoinPath($volume, $directoryString, $file);
+        };
+
+    return $path;
+    };
+
+
+#
+#   Function: PathIsAbsolute
+#
+#   Returns whether the passed path is absolute.
+#
+sub PathIsAbsolute #(path)
+    {
+    my ($self, $path) = @_;
+    return File::Spec->file_name_is_absolute($path);
+    };
+
+
+#
+#   Function: JoinPath
+#
+#   Creates a path from its elements.
+#
+#   Parameters:
+#
+#       volume - The volume, such as the drive letter on Windows.  Undef if none.
+#       dirString - The directory string.  Create with <JoinDirectories()> if necessary.
+#       file - The file name, or undef if none.
+#
+#   Returns:
+#
+#       The joined path.
+#
+sub JoinPath #(volume, dirString, $file)
+    {
+    my ($self, $volume, $dirString, $file) = @_;
+    return File::Spec->catpath($volume, $dirString, $file);
+    };
+
+
+#
+#   Function: JoinPaths
+#
+#   Joins two paths.
+#
+#   Parameters:
+#
+#       basePath       - May be a relative path, an absolute path, or undef.
+#       extraPath      - May be a relative path, a file, a relative path and file together, or undef.
+#       noFileInExtra - Set this to true if extraPath is a relative path only, and doesn't have a file.
+#
+#   Returns:
+#
+#       The joined path.
+#
+#   Why oh why?:
+#
+#       Because nothing in File::Spec will simply slap two paths together.  They have to be split up for catpath/file, and rel2abs
+#       requires the base to be absolute.
+#
+sub JoinPaths #(basePath, extraPath, noFileInExtra)
+    {
+    my ($self, $basePath, $extraPath, $noFileInExtra) = @_;
+
+    # If both are undef, it will return undef, which is what we want.
+    if (!defined $basePath)
+        {  return $extraPath;  }
+    elsif (!defined $extraPath)
+        {  return $basePath;  };
+
+    my ($baseVolume, $baseDirString, $baseFile) = File::Spec->splitpath($basePath, 1);
+    my ($extraVolume, $extraDirString, $extraFile) = File::Spec->splitpath($extraPath, $noFileInExtra);
+
+    my @baseDirectories = $self->SplitDirectories($baseDirString);
+    my @extraDirectories = $self->SplitDirectories($extraDirString);
+
+    my $fullDirString = $self->JoinDirectories(@baseDirectories, @extraDirectories);
+
+    my $fullPath = File::Spec->catpath($baseVolume, $fullDirString, $extraFile);
+
+    return $self->CanonizePath($fullPath);
+    };
+
+
+#
+#   Function: SplitPath
+#
+#   Takes a path and returns its elements.
+#
+#   Parameters:
+#
+#       path - The path to split.
+#       noFile - Set to true if the path doesn't have a file at the end.
+#
+#   Returns:
+#
+#       The array ( volume, directoryString, file ).  If any don't apply, they will be undef.  Use <SplitDirectories()> to split the
+#       directory string if desired.
+#
+#   Why oh Why?:
+#
+#       Because File::Spec->splitpath may leave a trailing slash/backslash/whatever on the directory string, which makes
+#       it a bit hard to match it with results from File::Spec->catdir.
+#
+sub SplitPath #(path, noFile)
+    {
+    my ($self, $path, $noFile) = @_;
+
+    my @segments = File::Spec->splitpath($path, $noFile);
+
+    if (!length $segments[0])
+        {  $segments[0] = undef;  };
+    if (!length $segments[2])
+        {  $segments[2] = undef;  };
+
+    $segments[1] = File::Spec->catdir( File::Spec->splitdir($segments[1]) );
+
+    return @segments;
+    };
+
+
+#
+#   Function: JoinDirectories
+#
+#   Creates a directory string from an array of directory names.
+#
+#   Parameters:
+#
+#       directory - A directory name.  There may be as many of these as desired.
+#
+sub JoinDirectories #(directory, directory, ...)
+    {
+    my ($self, @directories) = @_;
+    return File::Spec->catdir(@directories);
+    };
+
+
+#
+#   Function: SplitDirectories
+#
+#   Takes a string of directories and returns an array of its elements.
+#
+#   Why oh why?:
+#
+#       Because File::Spec->splitdir might leave an empty element at the end of the array, which screws up both joining in
+#       <ConvertToURL> and navigation in <MakeRelativePath>.
+#
+sub SplitDirectories #(directoryString)
+    {
+    my ($self, $directoryString) = @_;
+
+    my @directories = File::Spec->splitdir($directoryString);
+
+    if (!length $directories[-1])
+        {  pop @directories;  };
+
+    return @directories;
+    };
+
+
+#
+#   Function: MakeRelativePath
+#
+#   Takes two paths and returns a relative path between them.
+#
+#   Parameters:
+#
+#       basePath    - The starting path.  May be relative or absolute, so long as the target path is as well.
+#       targetPath  - The target path.  May be relative or absolute, so long as the base path is as well.
+#
+#       If both paths are relative, they are assumed to be relative to the same base.
+#
+#   Returns:
+#
+#       The target path relative to base.
+#
+#   Why oh why?:
+#
+#       First, there's nothing that gives a relative path between two relative paths.
+#
+#       Second, if target and base are absolute but on different volumes, File::Spec->abs2rel creates a totally non-functional
+#       relative path.  It should return the target as is, since there is no relative path.
+#
+#       Third, File::Spec->abs2rel between absolute paths on the same volume, at least on Windows, leaves the drive letter
+#       on.  So abs2rel('a:\b\c\d', 'a:\b') returns 'a:c\d' instead of the expected 'c\d'.  That makes no sense whatsoever.  It's
+#       not like it was designed to handle only directory names, either; the documentation says 'path' and the code seems to
+#       explicitly handle it.  There's just an 'unless' in there that tacks on the volume, defeating the purpose of a *relative* path
+#       and making the function worthless.
+#
+sub MakeRelativePath #(basePath, targetPath)
+    {
+    my ($self, $basePath, $targetPath) = @_;
+
+    my ($baseVolume, $baseDirString, $baseFile) = $self->SplitPath($basePath, 1);
+    my ($targetVolume, $targetDirString, $targetFile) = $self->SplitPath($targetPath);
+
+    # If the volumes are different, there is no possible relative path.
+    if ($targetVolume ne $baseVolume)
+        {  return $targetPath;  };
+
+    my @baseDirectories = $self->SplitDirectories($baseDirString);
+    my @targetDirectories = $self->SplitDirectories($targetDirString);
+
+    # Skip the parts of the path that are the same.
+    while (scalar @baseDirectories && @targetDirectories && $baseDirectories[0] eq $targetDirectories[0])
+        {
+        shift @baseDirectories;
+        shift @targetDirectories;
+        };
+
+    # Back out of the base path until it reaches where they were similar.
+    for (my $i = 0; $i < scalar @baseDirectories; $i++)
+        {
+        unshift @targetDirectories, File::Spec->updir();
+        };
+
+    $targetDirString = $self->JoinDirectories(@targetDirectories);
+
+    return File::Spec->catpath(undef, $targetDirString, $targetFile);
+    };
+
+
+#
+#   Function: IsSubPathOf
+#
+#   Returns whether the path is a descendant of another path.
+#
+#   Parameters:
+#
+#       base - The base path to test against.
+#       path - The possible subpath to test.
+#
+#   Returns:
+#
+#       Whether path is a descendant of base.
+#
+sub IsSubPathOf #(base, path)
+    {
+    my ($self, $base, $path) = @_;
+
+    # This is a quick test that should find a false quickly.
+    if ($base eq substr($path, 0, length($base)))
+        {
+        # This doesn't guarantee true, because it could be "C:\A B" and "C:\A B C\File".  So we test for it by seeing if the last
+        # directory in base is the same as the equivalent directory in path.
+
+        my ($baseVolume, $baseDirString, $baseFile) = NaturalDocs::File->SplitPath($base, 1);
+        my @baseDirectories = NaturalDocs::File->SplitDirectories($baseDirString);
+
+        my ($pathVolume, $pathDirString, $pathFile) = NaturalDocs::File->SplitPath($path);
+        my @pathDirectories = NaturalDocs::File->SplitDirectories($pathDirString);
+
+        return ( $baseDirectories[-1] eq $pathDirectories[ scalar @baseDirectories - 1 ] );
+        }
+    else
+        {  return undef;  };
+    };
+
+
+#
+#   Function: ConvertToURL
+#
+#   Takes a relative path and converts it from the native format to a relative URL.  Note that it _doesn't_ convert special characters
+#   to amp chars.
+#
+sub ConvertToURL #(path)
+    {
+    my ($self, $path) = @_;
+
+    my ($pathVolume, $pathDirString, $pathFile) = $self->SplitPath($path);
+    my @pathDirectories = $self->SplitDirectories($pathDirString);
+
+    my $i = 0;
+    while ($i < scalar @pathDirectories && $pathDirectories[$i] eq File::Spec->updir())
+        {
+        $pathDirectories[$i] = '..';
+        $i++;
+        };
+
+    return join('/', @pathDirectories, $pathFile);
+    };
+
+
+#
+#   Function: NoUpwards
+#
+#   Takes an array of directory entries and returns one without all the entries that refer to the parent directory, such as '.' and '..'.
+#
+sub NoUpwards #(array)
+    {
+    my ($self, @array) = @_;
+    return File::Spec->no_upwards(@array);
+    };
+
+
+#
+#   Function: NoFileName
+#
+#   Takes a path and returns a version without the file name.  Useful for sending paths to <CreatePath()>.
+#
+sub NoFileName #(path)
+    {
+    my ($self, $path) = @_;
+
+    my ($pathVolume, $pathDirString, $pathFile) = File::Spec->splitpath($path);
+
+    return File::Spec->catpath($pathVolume, $pathDirString, undef);
+    };
+
+
+#
+#   Function: NoExtension
+#
+#   Returns the path without an extension.
+#
+sub NoExtension #(path)
+    {
+    my ($self, $path) = @_;
+
+    my $extension = $self->ExtensionOf($path);
+
+    if ($extension)
+        {  $path = substr($path, 0, length($path) - length($extension) - 1);  };
+
+    return $path;
+    };
+
+
+#
+#   Function: ExtensionOf
+#
+#   Returns the extension of the passed path, or undef if none.
+#
+sub ExtensionOf #(path)
+    {
+    my ($self, $path) = @_;
+
+    my ($pathVolume, $pathDirString, $pathFile) = File::Spec->splitpath($path);
+
+    # We need the leading dot in the regex so files that start with a dot but don't have an extension count as extensionless files.
+    if ($pathFile =~ /.\.([^\.]+)$/)
+        {  return $1;  }
+    else
+        {  return undef;  };
+    };
+
+
+#
+#   Function: IsCaseSensitive
+#
+#   Returns whether the current platform has case-sensitive paths.
+#
+sub IsCaseSensitive
+    {
+    return !(File::Spec->case_tolerant());
+    };
+
+
+
+###############################################################################
+# Group: Disk Functions
+
+
+#
+#   Function: CreatePath
+#
+#   Creates a directory tree corresponding to the passed path, regardless of how many directories do or do not already exist.
+#   Do _not_ include a file name in the path.  Use <NoFileName()> first if you need to.
+#
+sub CreatePath #(path)
+    {
+    my ($self, $path) = @_;
+    File::Path::mkpath($path);
+    };
+
+
+#
+#   Function: RemoveEmptyTree
+#
+#   Removes an empty directory tree.  The passed directory will be removed if it's empty, and it will keep removing its parents
+#   until it reaches one that's not empty or a set limit.
+#
+#   Parameters:
+#
+#       path - The path to start from.  It will try to remove this directory and work it's way down.
+#       limit - The path to stop at if it doesn't find any non-empty directories first.  This path will *not* be removed.
+#
+sub RemoveEmptyTree #(path, limit)
+    {
+    my ($self, $path, $limit) = @_;
+
+    my ($volume, $directoryString) = $self->SplitPath($path, 1);
+    my @directories = $self->SplitDirectories($directoryString);
+
+    my $directory = $path;
+
+    while (-d $directory && $directory ne $limit)
+        {
+        opendir FH_ND_FILE, $directory;
+        my @entries = readdir FH_ND_FILE;
+        closedir FH_ND_FILE;
+
+        @entries = $self->NoUpwards(@entries);
+
+        if (scalar @entries || !rmdir($directory))
+            {  last;  };
+
+        pop @directories;
+        $directoryString = $self->JoinDirectories(@directories);
+        $directory = $self->JoinPath($volume, $directoryString);
+        };
+    };
+
+
+#
+#   Function: Copy
+#
+#   Copies a file from one path to another.  If the destination file exists, it is overwritten.
+#
+#   Parameters:
+#
+#       source       - The file to copy.
+#       destination - The destination to copy to.
+#
+#   Returns:
+#
+#       Whether it succeeded
+#
+sub Copy #(source, destination) => bool
+    {
+    my ($self, $source, $destination) = @_;
+    return File::Copy::copy($source, $destination);
+    };
+
+
+1;

Added: trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/ImageReferenceTable/Reference.pm
===================================================================
--- trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/ImageReferenceTable/Reference.pm	                        (rev 0)
+++ trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/ImageReferenceTable/Reference.pm	2009-07-01 15:30:00 UTC (rev 4275)
@@ -0,0 +1,44 @@
+###############################################################################
+#
+#   Package: NaturalDocs::ImageReferenceTable::Reference
+#
+###############################################################################
+#
+#   A class for references being tracked in <NaturalDocs::SourceDB>.
+#
+###############################################################################
+
+# 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::ImageReferenceTable::Reference;
+
+use base 'NaturalDocs::SourceDB::Item';
+
+
+use NaturalDocs::DefineMembers 'TARGET', 'Target()', 'SetTarget()',
+                                                 'NEEDS_REBUILD', 'NeedsRebuild()', 'SetNeedsRebuild()';
+
+
+#
+#   Variables: Members
+#
+#   The following constants are indexes into the object array.
+#
+#   TARGET - The image <FileName> this reference resolves to, or undef if none.
+#
+
+
+#
+#   Functions: Member Functions
+#
+#   Target - Returns the image <FileName> this reference resolves to, or undef if none.
+#   SetTarget - Replaces the image <FileName> this reference resolves to, or undef if none.
+#
+
+
+1;

Added: trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/ImageReferenceTable/String.pm
===================================================================
--- trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/ImageReferenceTable/String.pm	                        (rev 0)
+++ trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/ImageReferenceTable/String.pm	2009-07-01 15:30:00 UTC (rev 4275)
@@ -0,0 +1,110 @@
+###############################################################################
+#
+#   Package: NaturalDocs::ImageReferenceTable::String
+#
+###############################################################################
+#
+#   A package for creating and managing <ImageReferenceStrings>.
+#
+###############################################################################
+
+# 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::ImageReferenceTable::String;
+
+
+#
+#   Type: ImageReferenceString
+#
+#   A string representing a unique image reference.  It's composed of the reference text and the directory of the source file.
+#   The source file name itself isn't included because two files in the same directory with the same reference text will always go
+#   to the same targets.
+#
+
+
+#
+#   Function: Make
+#
+#   Converts a source <FileName> and the reference text to an <ImageReferenceString>.
+#
+sub Make #(FileName sourceFile, string text) => ImageReferenceString
+    {
+    my ($self, $sourceFile, $text) = @_;
+
+    my $path = NaturalDocs::File->NoFileName($sourceFile);
+
+    # Condense whitespace and remove any separator characters.
+    $path =~ tr/ \t\r\n\x1C/ /s;
+    $text =~ tr/ \t\r\n\x1C/ /s;
+
+    return $path . "\x1C" . $text;
+    };
+
+
+#
+#   Function: InformationOf
+#
+#   Returns the information contained in the <ImageReferenceString> as the array ( path, text ).
+#
+sub InformationOf #(ImageReferenceString referenceString)
+    {
+    my ($self, $referenceString) = @_;
+    return split(/\x1C/, $referenceString);
+    };
+
+
+#
+#   Function: ToBinaryFile
+#
+#   Writes an <ImageReferenceString> to <NaturalDocs::BinaryFile>.  Can also encode an undef.
+#
+#   Format:
+#
+#       > [AString16: path] [AString16: reference text] ...
+#
+#       Undef is represented by the first AString16 being undef.
+#
+sub ToBinaryFile #(ImageReferenceString referenceString)
+    {
+    my ($self, $referenceString) = @_;
+
+    if (defined $referenceString)
+        {
+        my ($path, $text) = split(/\x1C/, $referenceString);
+
+        NaturalDocs::BinaryFile->WriteAString16($path);
+        NaturalDocs::BinaryFile->WriteAString16($text);
+        }
+    else
+        {
+        NaturalDocs::BinaryFile->WriteAString16(undef);
+        };
+    };
+
+
+#
+#   Function: FromBinaryFile
+#
+#   Loads an <ImageReferenceString> or undef from <NaturalDocs::BinaryFile> and returns it.
+#
+sub FromBinaryFile
+    {
+    my $self = shift;
+
+    my $path = NaturalDocs::BinaryFile->GetAString16();
+
+    if (!defined $path)
+        {  return undef;  };
+
+    my $text = NaturalDocs::BinaryFile->GetAString16();
+
+    return $path . "\x1C" . $text;
+    };
+
+
+1;

Added: trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/ImageReferenceTable.pm
===================================================================
--- trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/ImageReferenceTable.pm	                        (rev 0)
+++ trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/ImageReferenceTable.pm	2009-07-01 15:30:00 UTC (rev 4275)
@@ -0,0 +1,383 @@
+###############################################################################
+#
+#   Package: NaturalDocs::ImageReferenceTable
+#
+###############################################################################
+#
+#   A <NaturalDocs::SourceDB>-based package that manages all the image references appearing in source files.
+#
+###############################################################################
+
+# 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;
+
+use NaturalDocs::ImageReferenceTable::String;
+use NaturalDocs::ImageReferenceTable::Reference;
+
+
+package NaturalDocs::ImageReferenceTable;
+
+use base 'NaturalDocs::SourceDB::Extension';
+
+
+###############################################################################
+# Group: Information
+
+#
+#   Topic: Usage
+#
+#       - <NaturalDocs::Project> and <NaturalDocs::SourceDB> must be initialized before this package can be used.
+#
+#       - Call <Register()> before using.
+#
+#
+#   Topic: Programming Notes
+#
+#       When working on this code, remember that there are three things it has to juggle.
+#
+#       - The information in <NaturalDocs::SourceDB>.
+#       - Image file references in <NaturalDocs::Project>.
+#       - Source file rebuilding on changes.
+#
+#       Managing the actual image files will be handled between <NaturalDocs::Project> and the <NaturalDocs::Builder>
+#       sub-packages.
+#
+#
+#   Topic: Implementation
+#
+#       Managing image references is simpler than managing the references in <NaturalDocs::SymbolTable>.  In SymbolTable,
+#       you have to worry about reference targets popping into and out of existence.  A link may go to a file that hasn't been
+#       reparsed yet and the target may no longer exist.  We have to deal with that when we know it, which may be after the
+#       reference's file was parsed.  Also, a new definition may appear that serves as a better interpretation of a link than its
+#       current target, and again we may only know that after the reference's file has been parsed already.  So we have to deal
+#       with scores and potential symbols and each symbol knowing exactly what links to it and so forth.
+#
+#       Not so with image references.  All possible targets (all possible image files) are known by <NaturalDocs::Project> early
+#       on and will remain consistent throughout execution.  So because of that, we can get away with only storing reference
+#       counts with each image and determining exactly where a reference points to as we find them.
+#
+#       Reference counts are stored with the image file information in <NaturalDocs::Project>.  However, it is not loaded and
+#       saved to disk by it.  Rather, it is regenerated by this package when it loads <ImageReferenceTable.nd>.
+#       NaturalDocs::Project only stores the last modification time (so it can add files to the build list if they've changed) and
+#       whether it had any references at all on the last run (so it knows whether it should care if they've changed.)
+#       ImageReferenceTable.nd stores each reference's target, width, and height.  Whether their interpretations have changed is
+#       dealt with in the <Load()> function, again since the list of targets (image files) is constant.
+#
+#       The package is based on <NaturalDocs::SourceDB>, so read it's documentation for more information on how it works.
+#
+
+
+###############################################################################
+# Group: Variables
+
+
+#
+#   var: extensionID
+#   The <ExtensionID> granted by <NaturalDocs::SourceDB>.
+#
+my $extensionID;
+
+
+
+###############################################################################
+# Group: Files
+
+
+#
+#   File: ImageReferenceTable.nd
+#
+#   The data file which stores all the image references from the last run of Natural Docs.
+#
+#   Format:
+#
+#       > [Standard Binary Header]
+#
+#       It starts with the standard binary header from <NaturalDocs::BinaryFile>.
+#
+#       > [Image Reference String or undef]
+#       > [AString16: target file]
+#       > [UInt16: target width or 0]
+#       > [UInt16: target height or 0]
+#
+#       For each <ImageReferenceString>, it's target, width, and height are stored.  The target is needed so we can tell if it
+#       changed from the last run, and the dimensions are needed because if the target hasn't changed but the file's dimensions
+#       have, the source files need to be rebuilt.
+#
+#       <ImageReferenceStrings> are encoded by <NaturalDocs::ImageReferenceTable::String>.
+#
+#       > [AString16: definition file or undef] ...
+#
+#       Then comes a series of AString16s for all the files that define the reference until it hits an undef.
+#
+#       This whole series is repeated for each <ImageReferenceString> until it hits an undef.
+#
+#	Revisions:
+#
+#		1.4:
+#
+#			- The file was added to Natural Docs.
+#
+
+
+
+###############################################################################
+# Group: Functions
+
+
+#
+#   Function: Register
+#   Registers the package with <NaturalDocs::SourceDB>.
+#
+sub Register
+    {
+    my $self = shift;
+    $extensionID = NaturalDocs::SourceDB->RegisterExtension($self, 0);
+    };
+
+
+#
+#   Function: Load
+#
+#   Loads the data from <ImageReferenceTable.nd>.  Returns whether it was successful.
+#
+sub Load # => bool
+    {
+    my $self = shift;
+
+    if (NaturalDocs::Settings->RebuildData())
+        {  return 0;  };
+
+    # The file format hasn't changed since it was introduced.
+    if (!NaturalDocs::BinaryFile->OpenForReading( NaturalDocs::Project->DataFile('ImageReferenceTable.nd') ))
+        {  return 0;  };
+
+
+    # [Image Reference String or undef]
+    while (my $referenceString = NaturalDocs::ImageReferenceTable::String->FromBinaryFile())
+        {
+        NaturalDocs::SourceDB->AddItem($extensionID, $referenceString,
+                                                           NaturalDocs::ImageReferenceTable::Reference->New());
+
+        # [AString16: target file]
+        # [UInt16: target width or 0]
+        # [UInt16: target height or 0]
+
+        my $targetFile = NaturalDocs::BinaryFile->GetAString16();
+        my $width = NaturalDocs::BinaryFile->GetUInt16();
+        my $height = NaturalDocs::BinaryFile->GetUInt16();
+
+        my $newTargetFile = $self->SetReferenceTarget($referenceString);
+        my $newWidth;
+        my $newHeight;
+
+        if ($newTargetFile)
+            {
+            NaturalDocs::Project->AddImageFileReference($newTargetFile);
+            ($newWidth, $newHeight) = NaturalDocs::Project->ImageFileDimensions($newTargetFile);
+            };
+
+        my $rebuildDefinitions = ($newTargetFile ne $targetFile || $newWidth != $width || $newHeight != $height);
+
+
+        # [AString16: definition file or undef] ...
+        while (my $definitionFile = NaturalDocs::BinaryFile->GetAString16())
+            {
+            NaturalDocs::SourceDB->AddDefinition($extensionID, $referenceString, $definitionFile);
+
+            if ($rebuildDefinitions)
+                {  NaturalDocs::Project->RebuildFile($definitionFile);  };
+            };
+        };
+
+
+    NaturalDocs::BinaryFile->Close();
+    return 1;
+    };
+
+
+#
+#   Function: Save
+#
+#   Saves the data to <ImageReferenceTable.nd>.
+#
+sub Save
+    {
+    my $self = shift;
+
+    my $references = NaturalDocs::SourceDB->GetAllItemsHashRef($extensionID);
+
+    NaturalDocs::BinaryFile->OpenForWriting( NaturalDocs::Project->DataFile('ImageReferenceTable.nd') );
+
+    while (my ($referenceString, $referenceObject) = each %$references)
+        {
+        # [Image Reference String or undef]
+        # [AString16: target file]
+        # [UInt16: target width or 0]
+        # [UInt16: target height or 0]
+
+        NaturalDocs::ImageReferenceTable::String->ToBinaryFile($referenceString);
+
+        my $target = $referenceObject->Target();
+        my ($width, $height);
+
+        if ($target)
+            {  ($width, $height) = NaturalDocs::Project->ImageFileDimensions($target);  };
+
+        NaturalDocs::BinaryFile->WriteAString16( $referenceObject->Target() );
+        NaturalDocs::BinaryFile->WriteUInt16( ($width || 0) );
+        NaturalDocs::BinaryFile->WriteUInt16( ($height || 0) );
+
+        # [AString16: definition file or undef] ...
+
+        my $definitions = $referenceObject->GetAllDefinitionsHashRef();
+
+        foreach my $definition (keys %$definitions)
+            {  NaturalDocs::BinaryFile->WriteAString16($definition);  };
+
+        NaturalDocs::BinaryFile->WriteAString16(undef);
+        };
+
+    NaturalDocs::ImageReferenceTable::String->ToBinaryFile(undef);
+
+    NaturalDocs::BinaryFile->Close();
+    };
+
+
+#
+#   Function: AddReference
+#
+#   Adds a new image reference.
+#
+sub AddReference #(FileName file, string referenceText)
+    {
+    my ($self, $file, $referenceText) = @_;
+
+    my $referenceString = NaturalDocs::ImageReferenceTable::String->Make($file, $referenceText);
+
+    if (!NaturalDocs::SourceDB->HasItem($extensionID, $referenceString))
+        {
+        my $referenceObject = NaturalDocs::ImageReferenceTable::Reference->New();
+        NaturalDocs::SourceDB->AddItem($extensionID, $referenceString, $referenceObject);
+
+        my $target = $self->SetReferenceTarget($referenceString);
+        if ($target)
+            {  NaturalDocs::Project->AddImageFileReference($target);  };
+        };
+
+    NaturalDocs::SourceDB->AddDefinition($extensionID, $referenceString, $file);
+    };
+
+
+#
+#   Function: OnDeletedDefinition
+#
+#   Called for each definition deleted by <NaturalDocs::SourceDB>.  This is called *after* the definition has been deleted from
+#   the database, so don't expect to be able to read it.
+#
+sub OnDeletedDefinition #(ImageReferenceString referenceString, FileName file, bool wasLastDefinition)
+    {
+    my ($self, $referenceString, $file, $wasLastDefinition) = @_;
+
+    if ($wasLastDefinition)
+        {
+        my $referenceObject = NaturalDocs::SourceDB->GetItem($extensionID, $referenceString);
+        my $target = $referenceObject->Target();
+
+        if ($target)
+            {  NaturalDocs::Project->DeleteImageFileReference($target);  };
+
+        NaturalDocs::SourceDB->DeleteItem($extensionID, $referenceString);
+        };
+    };
+
+
+#
+#   Function: GetReferenceTarget
+#
+#   Returns the image file the reference resolves to, or undef if none.
+#
+#   Parameters:
+#
+#       sourceFile - The source <FileName> the reference appears in.
+#       text - The reference text.
+#
+sub GetReferenceTarget #(FileName sourceFile, string text) => FileName
+    {
+    my ($self, $sourceFile, $text) = @_;
+
+    my $referenceString = NaturalDocs::ImageReferenceTable::String->Make($sourceFile, $text);
+    my $reference = NaturalDocs::SourceDB->GetItem($extensionID, $referenceString);
+
+    if (!defined $reference)
+        {  return undef;  }
+    else
+        {  return $reference->Target();  };
+    };
+
+
+#
+#   Function: SetReferenceTarget
+#
+#   Determines the best target for the passed <ImageReferenceString> and sets it on the
+#   <NaturalDocs::ImageReferenceTable::Reference> object.  Returns the new target <FileName>.  Does *not* add any source
+#   files to the bulid list.
+#
+sub SetReferenceTarget #(ImageReferenceString referenceString) => FileName
+    {
+    my ($self, $referenceString) = @_;
+
+    my $referenceObject = NaturalDocs::SourceDB->GetItem($extensionID, $referenceString);
+    my ($sourcePath, $text) = NaturalDocs::ImageReferenceTable::String->InformationOf($referenceString);
+
+
+    # Try the path relative to the source file first.
+
+    my $target;
+
+    my $imageFile = NaturalDocs::File->JoinPaths($sourcePath, $text);
+    my $exists = NaturalDocs::Project->ImageFileExists($imageFile);
+
+
+    # Then try relative image directories.
+
+    if (!$exists)
+        {
+        my $relativeImageDirectories = NaturalDocs::Settings->RelativeImageDirectories();
+
+        for (my $i = 0; $i < scalar @$relativeImageDirectories && !$exists; $i++)
+            {
+            $imageFile = NaturalDocs::File->JoinPaths($sourcePath, $relativeImageDirectories->[$i], 1);
+            $imageFile = NaturalDocs::File->JoinPaths($imageFile, $text);
+
+            $exists = NaturalDocs::Project->ImageFileExists($imageFile);
+            };
+        };
+
+
+    # Then try absolute image directories.
+
+    if (!$exists)
+        {
+        my $imageDirectories = NaturalDocs::Settings->ImageDirectories();
+
+        for (my $i = 0; $i < scalar @$imageDirectories && !$exists; $i++)
+            {
+            $imageFile = NaturalDocs::File->JoinPaths($imageDirectories->[$i], $text);
+            $exists = NaturalDocs::Project->ImageFileExists($imageFile);
+            };
+        };
+
+
+    if ($exists)
+        {  $target = NaturalDocs::Project->ImageFileCapitalization($imageFile);  };
+    #else leave it as undef.
+
+    $referenceObject->SetTarget($target);
+    return $target;
+    };
+
+
+1;

Added: trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Languages/ActionScript.pm
===================================================================
--- trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Languages/ActionScript.pm	                        (rev 0)
+++ trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Languages/ActionScript.pm	2009-07-01 15:30:00 UTC (rev 4275)
@@ -0,0 +1,1473 @@
+###############################################################################
+#
+#   Class: NaturalDocs::Languages::ActionScript
+#
+###############################################################################
+#
+#   A subclass to handle the language variations of Flash ActionScript.
+#
+###############################################################################
+
+# 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::Languages::ActionScript;
+
+use base 'NaturalDocs::Languages::Advanced';
+
+
+################################################################################
+# Group: Constants and Types
+
+
+#
+#   Constants: XML Tag Type
+#
+#   XML_OPENING_TAG - The tag is an opening one, such as <tag>.
+#   XML_CLOSING_TAG - The tag is a closing one, such as </tag>.
+#   XML_SELF_CONTAINED_TAG - The tag is self contained, such as <tag />.
+#
+use constant XML_OPENING_TAG => 1;
+use constant XML_CLOSING_TAG => 2;
+use constant XML_SELF_CONTAINED_TAG => 3;
+
+
+################################################################################
+# Group: Package Variables
+
+#
+#   hash: classModifiers
+#   An existence hash of all the acceptable class modifiers.  The keys are in all lowercase.
+#
+my %classModifiers = ( 'dynamic' => 1,
+                                   'intrinsic' => 1,
+                                   'final' => 1,
+                                   'internal' => 1,
+                                   'public' => 1 );
+
+#
+#   hash: memberModifiers
+#   An existence hash of all the acceptable class member modifiers.  The keys are in all lowercase.
+#
+my %memberModifiers = ( 'public' => 1,
+                                        'private' => 1,
+                                        'protected' => 1,
+                                        'static' => 1,
+                                        'internal' => 1,
+                                        'override' => 1 );
+
+
+#
+#   hash: declarationEnders
+#   An existence hash of all the tokens that can end a declaration.  This is important because statements don't require a semicolon
+#   to end.  The keys are in all lowercase.
+#
+my %declarationEnders = ( ';' => 1,
+                                        '}' => 1,
+                                        '{' => 1,
+                                        'public' => 1,
+                                        'private' => 1,
+                                        'protected' => 1,
+                                        'static' => 1,
+                                        'internal' => 1,
+                                        'dynamic' => 1,
+                                        'intrinsic' => 1,
+                                        'final' => 1,
+                                        'override' => 1,
+                                        'class' => 1,
+                                        'interface' => 1,
+                                        'var' => 1,
+                                        'function' => 1,
+                                        'const' => 1,
+                                        'namespace' => 1,
+                                        'import' => 1 );
+
+
+#
+#   var: isEscaped
+#   Whether the current file being parsed uses escapement.
+#
+my $isEscaped;
+
+
+
+################################################################################
+# Group: Interface Functions
+
+
+#
+#   Function: PackageSeparator
+#   Returns the package separator symbol.
+#
+sub PackageSeparator
+    {  return '.';  };
+
+
+#
+#   Function: EnumValues
+#   Returns the <EnumValuesType> that describes how the language handles enums.
+#
+sub EnumValues
+    {  return ::ENUM_GLOBAL();  };
+
+
+#
+#   Function: ParseParameterLine
+#   Parses a prototype parameter line and returns it as a <NaturalDocs::Languages::Prototype::Parameter> object.
+#
+sub ParseParameterLine #(line)
+    {
+    my ($self, $line) = @_;
+
+    if ($line =~ /^ ?\.\.\.\ (.+)$/)
+        {
+        # This puts them in the wrong fields as $1 should be the name and ... should be the type.  However, this is necessary
+        # because the order in the source is reversed from other parameter declarations and it's more important for the output
+        # to match the source.
+        return NaturalDocs::Languages::Prototype::Parameter->New($1, undef, '...', undef, undef, undef);
+        }
+    else
+        {  return $self->ParsePascalParameterLine($line);  };
+    };
+
+
+#
+#   Function: TypeBeforeParameter
+#   Returns whether the type appears before the parameter in prototypes.
+#
+sub TypeBeforeParameter
+    {  return 0;  };
+
+
+#
+#   Function: PreprocessFile
+#
+#   If the file is escaped, strips out all unescaped code.  Will translate any unescaped comments into comments surrounded by
+#   "\x1C\x1D\x1E\x1F" and "\x1F\x1E\x1D" characters, so chosen because they are the same character lengths as <!-- and -->
+#   and will not appear in normal code.
+#
+sub PreprocessFile
+    {
+    my ($self, $lines) = @_;
+
+    if (!$isEscaped)
+        {  return;  };
+
+    use constant MODE_UNESCAPED_REGULAR => 1;
+    use constant MODE_UNESCAPED_PI => 2;
+    use constant MODE_UNESCAPED_CDATA => 3;
+    use constant MODE_UNESCAPED_COMMENT => 4;
+    use constant MODE_ESCAPED_UNKNOWN_CDATA => 5;
+    use constant MODE_ESCAPED_CDATA => 6;
+    use constant MODE_ESCAPED_NO_CDATA => 7;
+
+    my $mode = MODE_UNESCAPED_REGULAR;
+
+    for (my $i = 0; $i < scalar @$lines; $i++)
+        {
+        my @tokens = split(/(<[ \t]*\/?[ \t]*mx:Script[^>]*>|<\?|\?>|<\!--|-->|<\!\[CDATA\[|\]\]\>)/, $lines->[$i]);
+        my $newLine;
+
+        foreach my $token (@tokens)
+            {
+            if ($mode == MODE_UNESCAPED_REGULAR)
+                {
+                if ($token eq '<?')
+                    {  $mode = MODE_UNESCAPED_PI;  }
+                elsif ($token eq '<![CDATA[')
+                    {  $mode = MODE_UNESCAPED_CDATA;  }
+                elsif ($token eq '<!--')
+                    {
+                    $mode = MODE_UNESCAPED_COMMENT;
+                    $newLine .= "\x1C\x1D\x1E\x1F";
+                    }
+                elsif ($token =~ /^<[ \t]*mx:Script/)
+                    {  $mode = MODE_ESCAPED_UNKNOWN_CDATA;  };
+                }
+
+            elsif ($mode == MODE_UNESCAPED_PI)
+                {
+                if ($token eq '?>')
+                    {  $mode = MODE_UNESCAPED_REGULAR;  };
+                }
+
+            elsif ($mode == MODE_UNESCAPED_CDATA)
+                {
+                if ($token eq ']]>')
+                    {  $mode = MODE_UNESCAPED_REGULAR;  };
+                }
+
+            elsif ($mode == MODE_UNESCAPED_COMMENT)
+                {
+                if ($token eq '-->')
+                    {
+                    $mode = MODE_UNESCAPED_REGULAR;
+                    $newLine .= "\x1F\x1E\x1D";
+                    }
+                else
+                    {  $newLine .= $token;  };
+                }
+
+            elsif ($mode == MODE_ESCAPED_UNKNOWN_CDATA)
+                {
+                if ($token eq '<![CDATA[')
+                    {  $mode = MODE_ESCAPED_CDATA;  }
+                elsif ($token =~ /^<[ \t]*\/[ \t]*mx:Script/)
+                    {
+                    $mode = MODE_UNESCAPED_REGULAR;
+                    $newLine .= '; ';
+                    }
+                elsif ($token !~ /^[ \t]*$/)
+                    {
+                    $mode = MODE_ESCAPED_NO_CDATA;
+                    $newLine .= $token;
+                    };
+                }
+
+            elsif ($mode == MODE_ESCAPED_CDATA)
+                {
+                if ($token eq ']]>')
+                    {
+                    $mode = MODE_UNESCAPED_REGULAR;
+                    $newLine .= '; ';
+                    }
+                else
+                    {  $newLine .= $token;  };
+                }
+
+            else #($mode == MODE_ESCAPED_NO_CDATA)
+                {
+                if ($token =~ /^<[ \t]*\/[ \t]*mx:Script/)
+                    {
+                    $mode = MODE_UNESCAPED_REGULAR;
+                    $newLine .= '; ';
+                    }
+                else
+                    {  $newLine .= $token;  };
+                };
+
+            };
+
+        $lines->[$i] = $newLine;
+        };
+    };
+
+
+#
+#   Function: ParseFile
+#
+#   Parses the passed source file, sending comments acceptable for documentation to <NaturalDocs::Parser->OnComment()>.
+#
+#   Parameters:
+#
+#       sourceFile - The <FileName> to parse.
+#       topicList - A reference to the list of <NaturalDocs::Parser::ParsedTopics> being built by the file.
+#
+#   Returns:
+#
+#       The array ( autoTopics, scopeRecord ).
+#
+#       autoTopics - An arrayref of automatically generated topics from the file, or undef if none.
+#       scopeRecord - An arrayref of <NaturalDocs::Languages::Advanced::ScopeChanges>, or undef if none.
+#
+sub ParseFile #(sourceFile, topicsList)
+    {
+    my ($self, $sourceFile, $topicsList) = @_;
+
+    # The \x1# comment symbols are inserted by PreprocessFile() to stand in for XML comments in escaped files.
+    my @parseParameters = ( [ '//' ], [ '/*', '*/', "\x1C\x1D\x1E\x1F", "\x1F\x1E\x1D" ], [ '///' ], [ '/**', '*/' ] );
+
+    my $extension = lc(NaturalDocs::File->ExtensionOf($sourceFile));
+    $isEscaped = ($extension eq 'mxml');
+
+    $self->ParseForCommentsAndTokens($sourceFile, @parseParameters);
+
+    my $tokens = $self->Tokens();
+    my $index = 0;
+    my $lineNumber = 1;
+
+    while ($index < scalar @$tokens)
+        {
+        if ($self->TryToSkipWhitespace(\$index, \$lineNumber) ||
+            $self->TryToGetImport(\$index, \$lineNumber) ||
+            $self->TryToGetClass(\$index, \$lineNumber) ||
+            $self->TryToGetFunction(\$index, \$lineNumber) ||
+            $self->TryToGetVariable(\$index, \$lineNumber) )
+            {
+            # The functions above will handle everything.
+            }
+
+        elsif ($tokens->[$index] eq '{')
+            {
+            $self->StartScope('}', $lineNumber, undef, undef, undef);
+            $index++;
+            }
+
+        elsif ($tokens->[$index] eq '}')
+            {
+            if ($self->ClosingScopeSymbol() eq '}')
+                {  $self->EndScope($lineNumber);  };
+
+            $index++;
+            }
+
+        else
+            {
+            $self->SkipToNextStatement(\$index, \$lineNumber);
+            };
+        };
+
+
+    # Don't need to keep these around.
+    $self->ClearTokens();
+
+
+    my $autoTopics = $self->AutoTopics();
+
+    my $scopeRecord = $self->ScopeRecord();
+    if (defined $scopeRecord && !scalar @$scopeRecord)
+        {  $scopeRecord = undef;  };
+
+    return ( $autoTopics, $scopeRecord );
+    };
+
+
+
+################################################################################
+# Group: Statement Parsing Functions
+# All functions here assume that the current position is at the beginning of a statement.
+#
+# Note for developers: I am well aware that the code in these functions do not check if we're past the end of the tokens as
+# often as it should.  We're making use of the fact that Perl will always return undef in these cases to keep the code simpler.
+
+
+#
+#   Function: TryToGetIdentifier
+#
+#   Determines whether the position is at an identifier, and if so, skips it and returns the complete identifier as a string.  Returns
+#   undef otherwise.
+#
+#   Parameters:
+#
+#       indexRef - A reference to the current token index.
+#       lineNumberRef - A reference to the current line number.
+#       allowStar - If set, allows the last identifier to be a star.
+#
+sub TryToGetIdentifier #(indexRef, lineNumberRef, allowStar)
+    {
+    my ($self, $indexRef, $lineNumberRef, $allowStar) = @_;
+    my $tokens = $self->Tokens();
+
+    my $index = $$indexRef;
+
+    use constant MODE_IDENTIFIER_START => 1;
+    use constant MODE_IN_IDENTIFIER => 2;
+    use constant MODE_AFTER_STAR => 3;
+
+    my $identifier;
+    my $mode = MODE_IDENTIFIER_START;
+
+    while ($index < scalar @$tokens)
+        {
+        if ($mode == MODE_IDENTIFIER_START)
+            {
+            if ($tokens->[$index] =~ /^[a-z\$\_]/i)
+                {
+                $identifier .= $tokens->[$index];
+                $index++;
+
+                $mode = MODE_IN_IDENTIFIER;
+                }
+            elsif ($allowStar && $tokens->[$index] eq '*')
+                {
+                $identifier .= '*';
+                $index++;
+
+                $mode = MODE_AFTER_STAR;
+                }
+            else
+                {  return undef;  };
+            }
+
+        elsif ($mode == MODE_IN_IDENTIFIER)
+            {
+            if ($tokens->[$index] eq '.')
+                {
+                $identifier .= '.';
+                $index++;
+
+                $mode = MODE_IDENTIFIER_START;
+                }
+            elsif ($tokens->[$index] =~ /^[a-z0-9\$\_]/i)
+                {
+                $identifier .= $tokens->[$index];
+                $index++;
+                }
+            else
+                {  last;  };
+            }
+
+        else #($mode == MODE_AFTER_STAR)
+            {
+            if ($tokens->[$index] =~ /^[a-z0-9\$\_\.]/i)
+                {  return undef;  }
+            else
+                {  last;  };
+            };
+        };
+
+    # We need to check again because we may have run out of tokens after a dot.
+    if ($mode != MODE_IDENTIFIER_START)
+        {
+        $$indexRef = $index;
+        return $identifier;
+        }
+    else
+        {  return undef;  };
+    };
+
+
+#
+#   Function: TryToGetImport
+#
+#   Determines whether the position is at a import statement, and if so, adds it as a Using statement to the current scope, skips
+#   it, and returns true.
+#
+sub TryToGetImport #(indexRef, lineNumberRef)
+    {
+    my ($self, $indexRef, $lineNumberRef) = @_;
+    my $tokens = $self->Tokens();
+
+    my $index = $$indexRef;
+    my $lineNumber = $$lineNumberRef;
+
+    if ($tokens->[$index] ne 'import')
+        {  return undef;  };
+
+    $index++;
+    $self->TryToSkipWhitespace(\$index, \$lineNumber);
+
+    my $identifier = $self->TryToGetIdentifier(\$index, \$lineNumber, 1);
+    if (!$identifier)
+        {  return undef;  };
+
+
+    # Currently we implement importing by stripping the last package level and treating it as a using.  So "import p1.p2.p3" makes
+    # p1.p2 the using path, which is over-tolerant but that's okay.  "import p1.p2.*" is treated the same way, but in this case it's
+    # not over-tolerant.  If there's no dot, there's no point to including it.
+
+    if (index($identifier, '.') != -1)
+        {
+        $identifier =~ s/\.[^\.]+$//;
+        $self->AddUsing( NaturalDocs::SymbolString->FromText($identifier) );
+        };
+
+    $$indexRef = $index;
+    $$lineNumberRef = $lineNumber;
+
+    return 1;
+    };
+
+
+#
+#   Function: TryToGetClass
+#
+#   Determines whether the position is at a class declaration statement, and if so, generates a topic for it, skips it, and
+#   returns true.
+#
+#   Supported Syntaxes:
+#
+#       - Classes
+#       - Interfaces
+#       - Classes and interfaces with _global
+#
+sub TryToGetClass #(indexRef, lineNumberRef)
+    {
+    my ($self, $indexRef, $lineNumberRef) = @_;
+    my $tokens = $self->Tokens();
+
+    my $index = $$indexRef;
+    my $lineNumber = $$lineNumberRef;
+
+    my @modifiers;
+
+    while ($tokens->[$index] =~ /^[a-z]/i &&
+              exists $classModifiers{lc($tokens->[$index])} )
+        {
+        push @modifiers, lc($tokens->[$index]);
+        $index++;
+
+        $self->TryToSkipWhitespace(\$index, \$lineNumber);
+        };
+
+    my $type;
+
+    if ($tokens->[$index] eq 'class' || $tokens->[$index] eq 'interface')
+        {
+        $type = $tokens->[$index];
+
+        $index++;
+        $self->TryToSkipWhitespace(\$index, \$lineNumber);
+        }
+    else
+        {  return undef;  };
+
+    my $className = $self->TryToGetIdentifier(\$index, \$lineNumber);
+
+    if (!$className)
+        {  return undef;  };
+
+    $self->TryToSkipWhitespace(\$index, \$lineNumber);
+
+    my @parents;
+
+    if ($tokens->[$index] eq 'extends')
+        {
+        $index++;
+        $self->TryToSkipWhitespace(\$index, \$lineNumber);
+
+        my $parent = $self->TryToGetIdentifier(\$index, \$lineNumber);
+        if (!$parent)
+            {  return undef;  };
+
+        push @parents, $parent;
+
+        $self->TryToSkipWhitespace(\$index, \$lineNumber);
+        };
+
+    if ($type eq 'class' && $tokens->[$index] eq 'implements')
+        {
+        $index++;
+        $self->TryToSkipWhitespace(\$index, \$lineNumber);
+
+        for (;;)
+            {
+            my $parent = $self->TryToGetIdentifier(\$index, \$lineNumber);
+            if (!$parent)
+                {  return undef;  };
+
+            push @parents, $parent;
+
+            $self->TryToSkipWhitespace(\$index, \$lineNumber);
+
+            if ($tokens->[$index] ne ',')
+                {  last;  }
+            else
+                {
+                $index++;
+                $self->TryToSkipWhitespace(\$index, \$lineNumber);
+                };
+            };
+        };
+
+    if ($tokens->[$index] ne '{')
+        {  return undef;  };
+
+    $index++;
+
+
+    # If we made it this far, we have a valid class declaration.
+
+    my $topicType;
+
+    if ($type eq 'interface')
+        {  $topicType = ::TOPIC_INTERFACE();  }
+    else
+        {  $topicType = ::TOPIC_CLASS();  };
+
+    $className =~ s/^_global.//;
+
+    my $autoTopic = NaturalDocs::Parser::ParsedTopic->New($topicType, $className,
+                                                                                         undef, $self->CurrentUsing(),
+                                                                                         undef,
+                                                                                         undef, undef, $$lineNumberRef);
+
+    $self->AddAutoTopic($autoTopic);
+    NaturalDocs::Parser->OnClass($autoTopic->Package());
+
+    foreach my $parent (@parents)
+        {
+        NaturalDocs::Parser->OnClassParent($autoTopic->Package(), NaturalDocs::SymbolString->FromText($parent),
+                                                               undef, $self->CurrentUsing(), ::RESOLVE_ABSOLUTE());
+        };
+
+    $self->StartScope('}', $lineNumber, $autoTopic->Package());
+
+    $$indexRef = $index;
+    $$lineNumberRef = $lineNumber;
+
+    return 1;
+    };
+
+
+#
+#   Function: TryToGetFunction
+#
+#   Determines if the position is on a function declaration, and if so, generates a topic for it, skips it, and returns true.
+#
+#   Supported Syntaxes:
+#
+#       - Functions
+#       - Constructors
+#       - Properties
+#       - Functions with _global
+#       - Functions with namespaces
+#
+sub TryToGetFunction #(indexRef, lineNumberRef)
+    {
+    my ($self, $indexRef, $lineNumberRef) = @_;
+    my $tokens = $self->Tokens();
+
+    my $index = $$indexRef;
+    my $lineNumber = $$lineNumberRef;
+
+    my $startIndex = $index;
+    my $startLine = $lineNumber;
+
+    my @modifiers;
+    my $namespace;
+
+    while ($tokens->[$index] =~ /^[a-z]/i)
+        {
+        if ($tokens->[$index] eq 'function')
+            {  last;  }
+
+        elsif (exists $memberModifiers{lc($tokens->[$index])})
+            {
+            push @modifiers, lc($tokens->[$index]);
+            $index++;
+
+            $self->TryToSkipWhitespace(\$index, \$lineNumber);
+            }
+
+        elsif (!$namespace)
+            {
+            do
+                {
+                $namespace .= $tokens->[$index];
+                $index++;
+                }
+            while ($tokens->[$index] =~ /^[a-z0-9_]/i);
+
+            $self->TryToSkipWhitespace(\$index, \$lineNumber);
+            }
+
+        else
+            {  last;  };
+        };
+
+    if ($tokens->[$index] ne 'function')
+        {  return undef;  };
+    $index++;
+
+    $self->TryToSkipWhitespace(\$index, \$lineNumber);
+
+    my $type;
+
+    if ($tokens->[$index] eq 'get' || $tokens->[$index] eq 'set')
+        {
+        # This can either be a property ("function get Something()") or a function name ("function get()").
+
+        my $nextIndex = $index;
+        my $nextLineNumber = $lineNumber;
+
+        $nextIndex++;
+        $self->TryToSkipWhitespace(\$nextIndex, \$nextLineNumber);
+
+        if ($tokens->[$nextIndex] eq '(')
+            {
+            $type = ::TOPIC_FUNCTION();
+            # Ignore the movement and let the code ahead pick it up as the name.
+            }
+        else
+            {
+            $type = ::TOPIC_PROPERTY();
+            $index = $nextIndex;
+            $lineNumber = $nextLineNumber;
+            };
+        }
+    else
+        {  $type = ::TOPIC_FUNCTION();  };
+
+    my $name = $self->TryToGetIdentifier(\$index, \$lineNumber);
+    if (!$name)
+        {  return undef;  };
+
+    $self->TryToSkipWhitespace(\$index, \$lineNumber);
+
+    if ($tokens->[$index] ne '(')
+        {  return undef;  };
+
+    $index++;
+    $self->GenericSkipUntilAfter(\$index, \$lineNumber, ')');
+
+    $self->TryToSkipWhitespace(\$index, \$lineNumber);
+
+    if ($tokens->[$index] eq ':')
+        {
+        $index++;
+
+        $self->TryToSkipWhitespace(\$index, \$lineNumber);
+
+        $self->TryToGetIdentifier(\$index, \$lineNumber, 1);
+
+        $self->TryToSkipWhitespace(\$index, \$lineNumber);
+        };
+
+
+    my $prototype = $self->NormalizePrototype( $self->CreateString($startIndex, $index) );
+
+    if ($tokens->[$index] eq '{')
+        {  $self->GenericSkip(\$index, \$lineNumber);  }
+    elsif (!exists $declarationEnders{$tokens->[$index]})
+        {  return undef;  };
+
+
+    my $scope = $self->CurrentScope();
+
+    if ($name =~ s/^_global.//)
+        {  $scope = undef;  };
+    if ($namespace)
+        {  $scope = NaturalDocs::SymbolString->Join($scope, $namespace);  };
+
+    $self->AddAutoTopic(NaturalDocs::Parser::ParsedTopic->New($type, $name,
+                                                                                              $scope, $self->CurrentUsing(),
+                                                                                              $prototype,
+                                                                                              undef, undef, $startLine));
+
+
+    # We succeeded if we got this far.
+
+    $$indexRef = $index;
+    $$lineNumberRef = $lineNumber;
+
+    return 1;
+    };
+
+
+#
+#   Function: TryToGetVariable
+#
+#   Determines if the position is on a variable declaration statement, and if so, generates a topic for each variable, skips the
+#   statement, and returns true.
+#
+#   Supported Syntaxes:
+#
+#       - Variables
+#       - Variables with _global
+#       - Variables with type * (untyped)
+#       - Constants
+#       - Variables and constants with namespaces
+#
+sub TryToGetVariable #(indexRef, lineNumberRef)
+    {
+    my ($self, $indexRef, $lineNumberRef) = @_;
+    my $tokens = $self->Tokens();
+
+    my $index = $$indexRef;
+    my $lineNumber = $$lineNumberRef;
+
+    my $startIndex = $index;
+    my $startLine = $lineNumber;
+
+    my @modifiers;
+    my $namespace;
+
+    while ($tokens->[$index] =~ /^[a-z]/i)
+        {
+        if ($tokens->[$index] eq 'var' || $tokens->[$index] eq 'const')
+            {  last;  }
+
+        elsif (exists $memberModifiers{lc($tokens->[$index])})
+            {
+            push @modifiers, lc($tokens->[$index]);
+            $index++;
+
+            $self->TryToSkipWhitespace(\$index, \$lineNumber);
+            }
+
+        elsif (!$namespace)
+            {
+            do
+                {
+                $namespace .= $tokens->[$index];
+                $index++;
+                }
+            while ($tokens->[$index] =~ /^[a-z0-9_]/i);
+
+            $self->TryToSkipWhitespace(\$index, \$lineNumber);
+            }
+
+        else
+            {  last;  };
+        };
+
+    my $type;
+
+    if ($tokens->[$index] eq 'var')
+        {  $type = ::TOPIC_VARIABLE();  }
+    elsif ($tokens->[$index] eq 'const')
+        {  $type = ::TOPIC_CONSTANT();  }
+    else
+        {  return undef;  };
+    $index++;
+
+    $self->TryToSkipWhitespace(\$index, \$lineNumber);
+
+    my $endTypeIndex = $index;
+    my @names;
+    my @types;
+
+    for (;;)
+        {
+        my $name = $self->TryToGetIdentifier(\$index, \$lineNumber);
+        if (!$name)
+            {  return undef;  };
+
+        $self->TryToSkipWhitespace(\$index, \$lineNumber);
+
+        my $type;
+
+        if ($tokens->[$index] eq ':')
+            {
+            $index++;
+            $self->TryToSkipWhitespace(\$index, \$lineNumber);
+
+            $type = ': ' . $self->TryToGetIdentifier(\$index, \$lineNumber, 1);
+
+            $self->TryToSkipWhitespace(\$index, \$lineNumber);
+            };
+
+        if ($tokens->[$index] eq '=')
+            {
+            do
+                {
+                $self->GenericSkip(\$index, \$lineNumber);
+                }
+            while ($tokens->[$index] ne ',' && !exists $declarationEnders{$tokens->[$index]} && $index < scalar @$tokens);
+            };
+
+        push @names, $name;
+        push @types, $type;
+
+        if ($tokens->[$index] eq ',')
+            {
+            $index++;
+            $self->TryToSkipWhitespace(\$index, \$lineNumber);
+            }
+        elsif (exists $declarationEnders{$tokens->[$index]})
+            {  last;  }
+        else
+            {  return undef;  };
+        };
+
+
+    # We succeeded if we got this far.
+
+    my $prototypePrefix = $self->CreateString($startIndex, $endTypeIndex);
+
+    for (my $i = 0; $i < scalar @names; $i++)
+        {
+        my $prototype = $self->NormalizePrototype( $prototypePrefix . ' ' . $names[$i] . $types[$i]);
+        my $scope = $self->CurrentScope();
+
+        if ($names[$i] =~ s/^_global.//)
+            {  $scope = undef;  };
+        if ($namespace)
+            {  $scope = NaturalDocs::SymbolString->Join($scope, $namespace);  };
+
+        $self->AddAutoTopic(NaturalDocs::Parser::ParsedTopic->New($type, $names[$i],
+                                                                                                  $scope, $self->CurrentUsing(),
+                                                                                                  $prototype,
+                                                                                                  undef, undef, $startLine));
+        };
+
+    $$indexRef = $index;
+    $$lineNumberRef = $lineNumber;
+
+    return 1;
+    };
+
+
+
+################################################################################
+# Group: Low Level Parsing Functions
+
+
+#
+#   Function: GenericSkip
+#
+#   Advances the position one place through general code.
+#
+#   - If the position is on a string, it will skip it completely.
+#   - If the position is on an opening symbol, it will skip until the past the closing symbol.
+#   - If the position is on whitespace (including comments), it will skip it completely.
+#   - Otherwise it skips one token.
+#
+#   Parameters:
+#
+#       indexRef - A reference to the current index.
+#       lineNumberRef - A reference to the current line number.
+#
+sub GenericSkip #(indexRef, lineNumberRef)
+    {
+    my ($self, $indexRef, $lineNumberRef) = @_;
+    my $tokens = $self->Tokens();
+
+    # We can ignore the scope stack because we're just skipping everything without parsing, and we need recursion anyway.
+    if ($tokens->[$$indexRef] eq '{')
+        {
+        $$indexRef++;
+        $self->GenericSkipUntilAfter($indexRef, $lineNumberRef, '}');
+        }
+    elsif ($tokens->[$$indexRef] eq '(')
+        {
+        $$indexRef++;
+        $self->GenericSkipUntilAfter($indexRef, $lineNumberRef, ')');
+        }
+    elsif ($tokens->[$$indexRef] eq '[')
+        {
+        $$indexRef++;
+        $self->GenericSkipUntilAfter($indexRef, $lineNumberRef, ']');
+        }
+
+    elsif ($self->TryToSkipWhitespace($indexRef, $lineNumberRef) ||
+            $self->TryToSkipString($indexRef, $lineNumberRef) ||
+            $self->TryToSkipRegExp($indexRef, $lineNumberRef) ||
+            $self->TryToSkipXML($indexRef, $lineNumberRef) )
+        {
+        }
+
+    else
+        {  $$indexRef++;  };
+    };
+
+
+#
+#   Function: GenericSkipUntilAfter
+#
+#   Advances the position via <GenericSkip()> until a specific token is reached and passed.
+#
+sub GenericSkipUntilAfter #(indexRef, lineNumberRef, token)
+    {
+    my ($self, $indexRef, $lineNumberRef, $token) = @_;
+    my $tokens = $self->Tokens();
+
+    while ($$indexRef < scalar @$tokens && $tokens->[$$indexRef] ne $token)
+        {  $self->GenericSkip($indexRef, $lineNumberRef);  };
+
+    if ($tokens->[$$indexRef] eq "\n")
+        {  $$lineNumberRef++;  };
+    $$indexRef++;
+    };
+
+
+#
+#   Function: IndiscriminateSkipUntilAfterSequence
+#
+#   Advances the position indiscriminately until a specific token sequence is reached and passed.
+#
+sub IndiscriminateSkipUntilAfterSequence #(indexRef, lineNumberRef, token, token, ...)
+    {
+    my ($self, $indexRef, $lineNumberRef, @sequence) = @_;
+    my $tokens = $self->Tokens();
+
+    while ($$indexRef < scalar @$tokens && !$self->IsAtSequence($$indexRef, @sequence))
+        {
+        if ($tokens->[$$indexRef] eq "\n")
+            {  $$lineNumberRef++;  };
+        $$indexRef++;
+        };
+
+    if ($self->IsAtSequence($$indexRef, @sequence))
+        {
+        $$indexRef += scalar @sequence;
+        foreach my $token (@sequence)
+            {
+            if ($token eq "\n")
+                {  $$lineNumberRef++;  };
+            };
+        };
+    };
+
+
+#
+#   Function: SkipToNextStatement
+#
+#   Advances the position via <GenericSkip()> until the next statement, which is defined as anything in <declarationEnders> not
+#   appearing in brackets or strings.  It will always advance at least one token.
+#
+sub SkipToNextStatement #(indexRef, lineNumberRef)
+    {
+    my ($self, $indexRef, $lineNumberRef) = @_;
+    my $tokens = $self->Tokens();
+
+    if ($tokens->[$$indexRef] eq ';')
+        {  $$indexRef++;  }
+
+    else
+        {
+        do
+            {
+            $self->GenericSkip($indexRef, $lineNumberRef);
+            }
+        while ( $$indexRef < scalar @$tokens &&
+                  !exists $declarationEnders{$tokens->[$$indexRef]} );
+        };
+    };
+
+
+#
+#   Function: TryToSkipRegExp
+#   If the current position is on a regular expression, skip past it and return true.
+#
+sub TryToSkipRegExp #(indexRef, lineNumberRef)
+    {
+    my ($self, $indexRef, $lineNumberRef) = @_;
+    my $tokens = $self->Tokens();
+
+    if ($tokens->[$$indexRef] eq '/')
+        {
+        # A slash can either start a regular expression or be a divide symbol.  Skip backwards to see what the previous symbol is.
+        my $index = $$indexRef - 1;
+
+        while ($index >= 0 && $tokens->[$index] =~ /^(?: |\t|\n)/)
+            {  $index--;  };
+
+        if ($index < 0 || $tokens->[$index] !~ /^\=\(\[\,]/)
+            {  return 0;  };
+
+        $$indexRef++;
+
+        while ($$indexRef < scalar @$tokens && $tokens->[$$indexRef] ne '/')
+            {
+            if ($tokens->[$$indexRef] eq '\\')
+                {  $$indexRef += 2;  }
+            elsif ($tokens->[$$indexRef] eq "\n")
+                {
+                $$indexRef++;
+                $$lineNumberRef++;
+                }
+            else
+                {  $$indexRef++;  }
+            };
+
+        if ($$indexRef < scalar @$tokens)
+            {
+            $$indexRef++;
+
+            if ($tokens->[$$indexRef] =~ /^[gimsx]+$/i)
+                {  $$indexRef++;  };
+            };
+
+        return 1;
+        }
+    else
+        {  return 0;  };
+    };
+
+
+#
+#   Function: TryToSkipXML
+#   If the current position is on an XML literal, skip past it and return true.
+#
+sub TryToSkipXML #(indexRef, lineNumberRef)
+    {
+    my ($self, $indexRef, $lineNumberRef) = @_;
+    my $tokens = $self->Tokens();
+
+    if ($tokens->[$$indexRef] eq '<')
+        {
+        # A < can either start an XML literal or be a comparison or shift operator.  First check the next character for << or <=.
+
+        my $index = $$indexRef + 1;
+
+        while ($index < scalar @$tokens && $tokens->[$index] =~ /^[\=\<]$/)
+            {  return 0;  };
+
+
+        # Next try the previous character.
+
+        $index = $$indexRef - 1;
+
+        while ($index >= 0 && $tokens->[$index] =~ /^[ |\t|\n]/)
+            {  $index--;  };
+
+        if ($index < 0 || $tokens->[$index] !~ /^[\=\(\[\,\>]/)
+            {  return 0;  };
+        }
+    else
+        {  return 0;  };
+
+
+    # Only handle the tag here if it's not an irregular XML section.
+    if (!$self->TryToSkipIrregularXML($indexRef, $lineNumberRef))
+        {
+        my @tagStack;
+
+        my ($tagType, $tagIdentifier) = $self->GetAndSkipXMLTag($indexRef, $lineNumberRef);
+        if ($tagType == XML_OPENING_TAG)
+            {  push @tagStack, $tagIdentifier;  };
+
+        while (scalar @tagStack && $$indexRef < scalar @$tokens)
+            {
+            $self->SkipToNextXMLTag($indexRef, $lineNumberRef);
+            ($tagType, $tagIdentifier) = $self->GetAndSkipXMLTag($indexRef, $lineNumberRef);
+
+            if ($tagType == XML_OPENING_TAG)
+                {  push @tagStack, $tagIdentifier;  }
+            elsif ($tagType == XML_CLOSING_TAG && $tagIdentifier eq $tagStack[-1])
+                {  pop @tagStack;  };
+            };
+        };
+
+
+    return 1;
+    };
+
+
+#
+#   Function: TryToSkipIrregularXML
+#
+#   If the current position is on an irregular XML tag, skip past it and return true.  Irregular XML tags are defined as
+#
+#       CDATA - <![CDATA[ ... ]]>
+#       Comments - <!-- ... -->
+#       PI - <? ... ?>
+#
+sub TryToSkipIrregularXML #(indexRef, lineNumberRef)
+    {
+    my ($self, $indexRef, $lineNumberRef) = @_;
+
+    if ($self->IsAtSequence($$indexRef, '<', '!', '[', 'CDATA', '['))
+        {
+        $$indexRef += 5;
+        $self->IndiscriminateSkipUntilAfterSequence($indexRef, $lineNumberRef, ']', ']', '>');
+        return 1;
+        }
+
+    elsif ($self->IsAtSequence($$indexRef, '<', '!', '-', '-'))
+        {
+        $$indexRef += 4;
+        $self->IndiscriminateSkipUntilAfterSequence($indexRef, $lineNumberRef, '-', '-', '>');
+        return 1;
+        }
+
+    elsif ($self->IsAtSequence($$indexRef, '<', '?'))
+        {
+        $$indexRef += 2;
+        $self->IndiscriminateSkipUntilAfterSequence($indexRef, $lineNumberRef, '?', '>');
+        return 1;
+        }
+
+    else
+        {  return 0;  };
+    };
+
+
+#
+#   Function: GetAndSkipXMLTag
+#
+#   Processes the XML tag at the current position, moves beyond it, and returns information about it.  Assumes the position is on
+#   the opening angle bracket of the tag and the tag is a normal XML tag, not one of the ones handled by
+#   <TryToSkipIrregularXML()>.
+#
+#   Parameters:
+#
+#       indexRef - A reference to the index of the position of the opening angle bracket.
+#       lineNumberRef - A reference to the line number of the position of the opening angle bracket.
+#
+#   Returns:
+#
+#       The array ( tagType, name ).
+#
+#       tagType - One of the <XML Tag Type> constants.
+#       identifier - The identifier of the tag.  If it's an empty tag (<> or </>), this will be "(anonymous)".
+#
+sub GetAndSkipXMLTag #(indexRef, lineNumberRef)
+    {
+    my ($self, $indexRef, $lineNumberRef) = @_;
+    my $tokens = $self->Tokens();
+
+    if ($$indexRef < scalar @$tokens && $tokens->[$$indexRef] ne '<')
+        {  die "Tried to call GetXMLTag when the position isn't on an opening bracket.";  };
+
+    # Get the anonymous ones out of the way so we don't have to worry about them below, since they're rather exceptional.
+
+    if ($self->IsAtSequence($$indexRef, '<', '>'))
+        {
+        $$indexRef += 2;
+        return ( XML_OPENING_TAG, '(anonymous)' );
+        }
+    elsif ($self->IsAtSequence($$indexRef, '<', '/', '>'))
+        {
+        $$indexRef += 3;
+        return ( XML_CLOSING_TAG, '(anonymous)' );
+        };
+
+
+    # Grab the identifier.
+
+    my $tagType = XML_OPENING_TAG;
+    my $identifier;
+
+    $$indexRef++;
+
+    if ($tokens->[$$indexRef] eq '/')
+        {
+        $$indexRef++;
+        $tagType = XML_CLOSING_TAG;
+        };
+
+    $self->TryToSkipXMLWhitespace($indexRef, $lineNumberRef);
+
+
+    # The identifier could be a native expression in braces.
+
+    if ($tokens->[$$indexRef] eq '{')
+        {
+        my $startOfIdentifier = $$indexRef;
+
+        $$indexRef++;
+        $self->GenericSkipUntilAfter($indexRef, $lineNumberRef, '}');
+
+        $identifier = $self->CreateString($startOfIdentifier, $$indexRef);
+        }
+
+
+    # Otherwise just grab content until whitespace or the end of the tag.
+
+    else
+        {
+        while ($$indexRef < scalar @$tokens && $tokens->[$$indexRef] !~ /^[\/\>\ \t]$/)
+            {
+            $identifier .= $tokens->[$$indexRef];
+            $$indexRef++;
+            };
+        };
+
+
+    # Skip to the end of the tag.
+
+    while ($$indexRef < scalar @$tokens && $tokens->[$$indexRef] !~ /^[\/\>]$/)
+        {
+        if ($tokens->[$$indexRef] eq '{')
+            {
+            $$indexRef++;
+            $self->GenericSkipUntilAfter($indexRef, $lineNumberRef, '}');
+            }
+
+        elsif ($self->TryToSkipXMLWhitespace($indexRef, $lineNumberRef))
+            {  }
+
+        # We don't need to do special handling for attribute quotes or anything like that because there's no backslashing in
+        # XML.  It's all handled with entity characters.
+        else
+            {  $$indexRef++;  };
+        };
+
+
+    if ($tokens->[$$indexRef] eq '/')
+        {
+        if ($tagType == XML_OPENING_TAG)
+            {  $tagType = XML_SELF_CONTAINED_TAG;  };
+
+        $$indexRef++;
+        };
+
+    if ($tokens->[$$indexRef] eq '>')
+        {  $$indexRef++;  };
+
+    if (!$identifier)
+        {  $identifier = '(anonymous)';  };
+
+
+    return ( $tagType, $identifier );
+    };
+
+
+#
+#   Function: SkipToNextXMLTag
+#   Skips to the next normal XML tag.  It will not stop at elements handled by <TryToSkipIrregularXML()>.  Note that if the
+#   position is already at an XML tag, it will not move.
+#
+sub SkipToNextXMLTag #(indexRef, lineNumberRef)
+    {
+    my ($self, $indexRef, $lineNumberRef) = @_;
+    my $tokens = $self->Tokens();
+
+    while ($$indexRef < scalar @$tokens)
+        {
+        if ($tokens->[$$indexRef] eq '{')
+            {
+            $$indexRef++;
+            $self->GenericSkipUntilAfter($indexRef, $lineNumberRef, '}');
+            }
+
+        elsif ($self->TryToSkipIrregularXML($indexRef, $lineNumberRef))
+            {  }
+
+        elsif ($tokens->[$$indexRef] eq '<')
+            {  last;  }
+
+        else
+            {
+            if ($tokens->[$$indexRef] eq "\n")
+                {  $$lineNumberRef++;  };
+
+            $$indexRef++;
+            };
+        };
+    };
+
+
+#
+#   Function: TryToSkipXMLWhitespace
+#   If the current position is on XML whitespace, skip past it and return true.
+#
+sub TryToSkipXMLWhitespace #(indexRef, lineNumberRef)
+    {
+    my ($self, $indexRef, $lineNumberRef) = @_;
+    my $tokens = $self->Tokens();
+
+    my $result;
+
+    while ($$indexRef < scalar @$tokens)
+        {
+        if ($tokens->[$$indexRef] =~ /^[ \t]/)
+            {
+            $$indexRef++;
+            $result = 1;
+            }
+        elsif ($tokens->[$$indexRef] eq "\n")
+            {
+            $$indexRef++;
+            $$lineNumberRef++;
+            $result = 1;
+            }
+        else
+            {  last;  };
+        };
+
+    return $result;
+    };
+
+
+#
+#   Function: TryToSkipString
+#   If the current position is on a string delimiter, skip past the string and return true.
+#
+#   Parameters:
+#
+#       indexRef - A reference to the index of the position to start at.
+#       lineNumberRef - A reference to the line number of the position.
+#
+#   Returns:
+#
+#       Whether the position was at a string.
+#
+#   Syntax Support:
+#
+#       - Supports quotes and apostrophes.
+#
+sub TryToSkipString #(indexRef, lineNumberRef)
+    {
+    my ($self, $indexRef, $lineNumberRef) = @_;
+
+    return ($self->SUPER::TryToSkipString($indexRef, $lineNumberRef, '\'') ||
+               $self->SUPER::TryToSkipString($indexRef, $lineNumberRef, '"') );
+    };
+
+
+#
+#   Function: TryToSkipWhitespace
+#   If the current position is on a whitespace token, a line break token, or a comment, it skips them and returns true.  If there are
+#   a number of these in a row, it skips them all.
+#
+sub TryToSkipWhitespace #(indexRef, lineNumberRef)
+    {
+    my ($self, $indexRef, $lineNumberRef) = @_;
+    my $tokens = $self->Tokens();
+
+    my $result;
+
+    while ($$indexRef < scalar @$tokens)
+        {
+        if ($tokens->[$$indexRef] =~ /^[ \t]/)
+            {
+            $$indexRef++;
+            $result = 1;
+            }
+        elsif ($tokens->[$$indexRef] eq "\n")
+            {
+            $$indexRef++;
+            $$lineNumberRef++;
+            $result = 1;
+            }
+        elsif ($self->TryToSkipComment($indexRef, $lineNumberRef))
+            {
+            $result = 1;
+            }
+        else
+            {  last;  };
+        };
+
+    return $result;
+    };
+
+
+#
+#   Function: TryToSkipComment
+#   If the current position is on a comment, skip past it and return true.
+#
+sub TryToSkipComment #(indexRef, lineNumberRef)
+    {
+    my ($self, $indexRef, $lineNumberRef) = @_;
+
+    return ( $self->TryToSkipLineComment($indexRef, $lineNumberRef) ||
+                $self->TryToSkipMultilineComment($indexRef, $lineNumberRef) );
+    };
+
+
+#
+#   Function: TryToSkipLineComment
+#   If the current position is on a line comment symbol, skip past it and return true.
+#
+sub TryToSkipLineComment #(indexRef, lineNumberRef)
+    {
+    my ($self, $indexRef, $lineNumberRef) = @_;
+    my $tokens = $self->Tokens();
+
+    if ($tokens->[$$indexRef] eq '/' && $tokens->[$$indexRef+1] eq '/')
+        {
+        $self->SkipRestOfLine($indexRef, $lineNumberRef);
+        return 1;
+        }
+    else
+        {  return undef;  };
+    };
+
+
+#
+#   Function: TryToSkipMultilineComment
+#   If the current position is on an opening comment symbol, skip past it and return true.
+#
+sub TryToSkipMultilineComment #(indexRef, lineNumberRef)
+    {
+    my ($self, $indexRef, $lineNumberRef) = @_;
+    my $tokens = $self->Tokens();
+
+    if ($tokens->[$$indexRef] eq '/' && $tokens->[$$indexRef+1] eq '*')
+        {
+        $self->SkipUntilAfter($indexRef, $lineNumberRef, '*', '/');
+        return 1;
+        }
+    else
+        {  return undef;  };
+    };
+
+
+1;

Added: trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Languages/Ada.pm
===================================================================
--- trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Languages/Ada.pm	                        (rev 0)
+++ trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Languages/Ada.pm	2009-07-01 15:30:00 UTC (rev 4275)
@@ -0,0 +1,38 @@
+###############################################################################
+#
+#   Class: NaturalDocs::Languages::Ada
+#
+###############################################################################
+#
+#   A subclass to handle the language variations of Ada
+#
+###############################################################################
+
+# 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::Languages::Ada;
+
+use base 'NaturalDocs::Languages::Simple';
+
+
+#
+#   Function: ParseParameterLine
+#   Overridden because Ada uses Pascal-style parameters
+#
+sub ParseParameterLine #(...)
+    {
+    my ($self, @params) = @_;
+    return $self->SUPER::ParsePascalParameterLine(@params);
+    };
+
+sub TypeBeforeParameter
+    {
+    return 0;
+    };
+
+
+1;

Added: trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Languages/Advanced/Scope.pm
===================================================================
--- trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Languages/Advanced/Scope.pm	                        (rev 0)
+++ trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Languages/Advanced/Scope.pm	2009-07-01 15:30:00 UTC (rev 4275)
@@ -0,0 +1,95 @@
+###############################################################################
+#
+#   Class: NaturalDocs::Languages::Advanced::Scope
+#
+###############################################################################
+#
+#   A class used to store a scope level.
+#
+###############################################################################
+
+# 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::Languages::Advanced::Scope;
+
+#
+#   Constants: Implementation
+#
+#   The object is implemented as a blessed arrayref.  The constants below are used as indexes.
+#
+#   CLOSING_SYMBOL - The closing symbol character of the scope.
+#   PACKAGE - The package <SymbolString> of the scope.
+#   USING - An arrayref of <SymbolStrings> for using statements, or undef if none.
+#
+use NaturalDocs::DefineMembers 'CLOSING_SYMBOL', 'PACKAGE', 'USING';
+# Dependency: New() depends on the order of these constants as well as that there is no inherited members.
+
+
+#
+#   Function: New
+#
+#   Creates and returns a new object.
+#
+#   Parameters:
+#
+#       closingSymbol - The closing symbol character of the scope.
+#       package - The package <SymbolString> of the scope.
+#       using - An arrayref of using <SymbolStrings>, or undef if none.  The contents of the array will be duplicated.
+#
+#       If package is set to undef, it is assumed that it inherits the value of the previous scope on the stack.
+#
+sub New #(closingSymbol, package, using)
+    {
+    # Dependency: This depends on the order of the parameters matching the constants, and that there are no inherited
+    # members.
+    my $package = shift;
+
+    my $object = [ @_ ];
+    bless $object, $package;
+
+    if (defined $object->[USING])
+        {  $object->[USING] = [ @{$object->[USING]} ];  };
+
+    return $object;
+    };
+
+
+# Function: ClosingSymbol
+# Returns the closing symbol character of the scope.
+sub ClosingSymbol
+    {  return $_[0]->[CLOSING_SYMBOL];  };
+
+# Function: Package
+# Returns the package <SymbolString> of the scope, or undef if none.
+sub Package
+    {  return $_[0]->[PACKAGE];  };
+
+# Function: SetPackage
+# Sets the package <SymbolString> of the scope.
+sub SetPackage #(package)
+    {  $_[0]->[PACKAGE] = $_[1];  };
+
+# Function: Using
+# Returns an arrayref of <SymbolStrings> for using statements, or undef if none
+sub Using
+    {  return $_[0]->[USING];  };
+
+# Function: AddUsing
+# Adds a <SymbolString> to the <Using()> array.
+sub AddUsing #(using)
+    {
+    my ($self, $using) = @_;
+
+    if (!defined $self->[USING])
+        {  $self->[USING] = [ ];  };
+
+    push @{$self->[USING]}, $using;
+    };
+
+
+
+1;

Added: trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Languages/Advanced/ScopeChange.pm
===================================================================
--- trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Languages/Advanced/ScopeChange.pm	                        (rev 0)
+++ trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Languages/Advanced/ScopeChange.pm	2009-07-01 15:30:00 UTC (rev 4275)
@@ -0,0 +1,70 @@
+###############################################################################
+#
+#   Class: NaturalDocs::Languages::Advanced::ScopeChange
+#
+###############################################################################
+#
+#   A class used to store a scope change.
+#
+###############################################################################
+
+# 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::Languages::Advanced::ScopeChange;
+
+#
+#   Constants: Implementation
+#
+#   The object is implemented as a blessed arrayref.  The constants below are used as indexes.
+#
+#   SCOPE - The new scope <SymbolString>.
+#   LINE_NUMBER - The line number of the change.
+#
+use NaturalDocs::DefineMembers 'SCOPE', 'LINE_NUMBER';
+# Dependency: New() depends on the order of these constants as well as that there is no inherited members.
+
+
+#
+#   Function: New
+#
+#   Creates and returns a new object.
+#
+#   Parameters:
+#
+#       scope - The <SymbolString> the scope was changed to.
+#       lineNumber - What line it occurred on.
+#
+sub New #(scope, lineNumber)
+    {
+    # Dependency: This depends on the order of the parameters matching the constants, and that there are no inherited
+    # members.
+    my $self = shift;
+
+    my $object = [ @_ ];
+    bless $object, $self;
+
+    return $object;
+    };
+
+
+# Function: Scope
+# Returns the <SymbolString> the scope was changed to.
+sub Scope
+    {  return $_[0]->[SCOPE];  };
+
+# Function: SetScope
+# Replaces the <SymbolString> the scope was changed to.
+sub SetScope #(scope)
+    {  $_[0]->[SCOPE] = $_[1];  };
+
+# Function: LineNumber
+# Returns the line number of the change.
+sub LineNumber
+    {  return $_[0]->[LINE_NUMBER];  };
+
+
+1;

Added: trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Languages/Advanced.pm
===================================================================
--- trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Languages/Advanced.pm	                        (rev 0)
+++ trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Languages/Advanced.pm	2009-07-01 15:30:00 UTC (rev 4275)
@@ -0,0 +1,828 @@
+###############################################################################
+#
+#   Class: NaturalDocs::Languages::Advanced
+#
+###############################################################################
+#
+#   The base class for all languages that have full support in Natural Docs.  Each one will have a custom parser capable
+#   of documenting undocumented aspects of the code.
+#
+###############################################################################
+
+# 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;
+
+use NaturalDocs::Languages::Advanced::Scope;
+use NaturalDocs::Languages::Advanced::ScopeChange;
+
+package NaturalDocs::Languages::Advanced;
+
+use base 'NaturalDocs::Languages::Base';
+
+
+#############################################################################
+# Group: Implementation
+
+#
+#   Constants: Members
+#
+#   The class is implemented as a blessed arrayref.  The following constants are used as indexes.
+#
+#   TOKENS - An arrayref of tokens used in all the <Parsing Functions>.
+#   SCOPE_STACK - An arrayref of <NaturalDocs::Languages::Advanced::Scope> objects serving as a scope stack for parsing.
+#                            There will always be one available, with a symbol of undef, for the top level.
+#   SCOPE_RECORD - An arrayref of <NaturalDocs::Languages::Advanced::ScopeChange> objects, as generated by the scope
+#                              stack.  If there is more than one change per line, only the last is stored.
+#   AUTO_TOPICS - An arrayref of <NaturalDocs::Parser::ParsedTopics> generated automatically from the code.
+#
+use NaturalDocs::DefineMembers 'TOKENS', 'SCOPE_STACK', 'SCOPE_RECORD', 'AUTO_TOPICS';
+
+
+#############################################################################
+# Group: Functions
+
+#
+#   Function: New
+#
+#   Creates and returns a new object.
+#
+#   Parameters:
+#
+#       name - The name of the language.
+#
+sub New #(name)
+    {
+    my ($package, @parameters) = @_;
+
+    my $object = $package->SUPER::New(@parameters);
+    $object->[TOKENS] = undef;
+    $object->[SCOPE_STACK] = undef;
+    $object->[SCOPE_RECORD] = undef;
+
+    return $object;
+    };
+
+
+# Function: Tokens
+# Returns the tokens found by <ParseForCommentsAndTokens()>.
+sub Tokens
+    {  return $_[0]->[TOKENS];  };
+
+# Function: SetTokens
+# Replaces the tokens.
+sub SetTokens #(tokens)
+    {  $_[0]->[TOKENS] = $_[1];  };
+
+# Function: ClearTokens
+#  Resets the token list.  You may want to do this after parsing is over to save memory.
+sub ClearTokens
+    {  $_[0]->[TOKENS] = undef;  };
+
+# Function: AutoTopics
+# Returns the arrayref of automatically generated topics, or undef if none.
+sub AutoTopics
+    {  return $_[0]->[AUTO_TOPICS];  };
+
+# Function: AddAutoTopic
+# Adds a <NaturalDocs::Parser::ParsedTopic> to <AutoTopics()>.
+sub AddAutoTopic #(topic)
+    {
+    my ($self, $topic) = @_;
+    if (!defined $self->[AUTO_TOPICS])
+        {  $self->[AUTO_TOPICS] = [ ];  };
+    push @{$self->[AUTO_TOPICS]}, $topic;
+    };
+
+# Function: ClearAutoTopics
+# Resets the automatic topic list.  Not necessary if you call <ParseForCommentsAndTokens()>.
+sub ClearAutoTopics
+    {  $_[0]->[AUTO_TOPICS] = undef;  };
+
+# Function: ScopeRecord
+# Returns an arrayref of <NaturalDocs::Languages::Advanced::ScopeChange> objects describing how and when the scope
+# changed thoughout the file.  There will always be at least one entry, which will be for line 1 and undef as the scope.
+sub ScopeRecord
+    {  return $_[0]->[SCOPE_RECORD];  };
+
+
+
+###############################################################################
+#
+#   Group: Parsing Functions
+#
+#   These functions are good general language building blocks.  Use them to create your language-specific parser.
+#
+#   All functions work on <Tokens()> and assume it is set by <ParseForCommentsAndTokens()>.
+#
+
+
+#
+#   Function: ParseForCommentsAndTokens
+#
+#   Loads the passed file, sends all appropriate comments to <NaturalDocs::Parser->OnComment()>, and breaks the rest into
+#   an arrayref of tokens.  Tokens are defined as
+#
+#   - All consecutive alphanumeric and underscore characters.
+#   - All consecutive whitespace.
+#   - A single line break.  It will always be "\n"; you don't have to worry about platform differences.
+#   - A single character not included above, which is usually a symbol.  Multiple consecutive ones each get their own token.
+#
+#   The result will be placed in <Tokens()>.
+#
+#   Parameters:
+#
+#       sourceFile - The source <FileName> to load and parse.
+#       lineCommentSymbols - An arrayref of symbols that designate line comments, or undef if none.
+#       blockCommentSymbols - An arrayref of symbol pairs that designate multiline comments, or undef if none.  Symbol pairs are
+#                                            designated as two consecutive array entries, the opening symbol appearing first.
+#       javadocLineCommentSymbols - An arrayref of symbols that designate the start of a JavaDoc comment, or undef if none.
+#       javadocBlockCommentSymbols - An arrayref of symbol pairs that designate multiline JavaDoc comments, or undef if none.
+#
+#   Notes:
+#
+#       - This function automatically calls <ClearAutoTopics()> and <ClearScopeStack()>.  You only need to call those functions
+#         manually if you override this one.
+#       - To save parsing time, all comment lines sent to <NaturalDocs::Parser->OnComment()> will be replaced with blank lines
+#         in <Tokens()>.  It's all the same to most languages.
+#
+sub ParseForCommentsAndTokens #(FileName sourceFile, string[] lineCommentSymbols, string[] blockCommentSymbols, string[] javadocLineCommentSymbols, string[] javadocBlockCommentSymbols)
+    {
+    my ($self, $sourceFile, $lineCommentSymbols, $blockCommentSymbols,
+           $javadocLineCommentSymbols, $javadocBlockCommentSymbols) = @_;
+
+    open(SOURCEFILEHANDLE, '<' . $sourceFile)
+        or die "Couldn't open input file " . $sourceFile . "\n";
+
+    my $tokens = [ ];
+    $self->SetTokens($tokens);
+
+    # For convenience.
+    $self->ClearAutoTopics();
+    $self->ClearScopeStack();
+
+
+    # Load and preprocess the file
+
+    my @lines;
+    my $line = <SOURCEFILEHANDLE>;
+
+    # On the very first line, remove a Unicode BOM if present.  Information on it available at:
+    # http://www.unicode.org/faq/utf_bom.html#BOM
+    $line =~ s/^\xEF\xBB\xBF//;
+
+    while (defined $line)
+        {
+        ::XChomp(\$line);
+        push @lines, $line;
+
+        $line = <SOURCEFILEHANDLE>;
+        };
+
+    close(SOURCEFILEHANDLE);
+
+    $self->PreprocessFile(\@lines);
+
+
+    # Go through the file
+
+    my $lineIndex = 0;
+
+    while ($lineIndex < scalar @lines)
+        {
+        $line = $lines[$lineIndex];
+
+        my @commentLines;
+        my $commentLineNumber;
+        my $isJavaDoc;
+        my $closingSymbol;
+
+
+        # Retrieve single line comments.  This leaves $lineIndex at the next line.
+
+        if ( ($isJavaDoc = $self->StripOpeningJavaDocSymbols(\$line, $javadocLineCommentSymbols)) ||
+              $self->StripOpeningSymbols(\$line, $lineCommentSymbols))
+            {
+            $commentLineNumber = $lineIndex + 1;
+
+            do
+                {
+                push @commentLines, $line;
+                push @$tokens, "\n";
+
+                $lineIndex++;
+
+                if ($lineIndex >= scalar @lines)
+                    {  goto EndDo;  };
+
+                $line = $lines[$lineIndex];
+                }
+            while ($self->StripOpeningSymbols(\$line, $lineCommentSymbols));
+
+            EndDo:  # I hate Perl sometimes.
+            }
+
+
+        # Retrieve multiline comments.  This leaves $lineIndex at the next line.
+
+        elsif ( ($isJavaDoc = $self->StripOpeningJavaDocBlockSymbols(\$line, $javadocBlockCommentSymbols)) ||
+                 ($closingSymbol = $self->StripOpeningBlockSymbols(\$line, $blockCommentSymbols)) )
+            {
+            $commentLineNumber = $lineIndex + 1;
+
+            if ($isJavaDoc)
+                {  $closingSymbol = $isJavaDoc;  };
+
+            # Note that it is possible for a multiline comment to start correctly but not end so.  We want those comments to stay in
+            # the code.  For example, look at this prototype with this splint annotation:
+            #
+            # int get_array(integer_t id,
+            #                    /*@out@*/ array_t array);
+            #
+            # The annotation starts correctly but doesn't end so because it is followed by code on the same line.
+
+            my ($lineRemainder, $isMultiLine);
+
+            for (;;)
+                {
+                $lineRemainder = $self->StripClosingSymbol(\$line, $closingSymbol);
+
+                push @commentLines, $line;
+
+                #  If we found an end comment symbol...
+                if (defined $lineRemainder)
+                    {  last;  };
+
+                push @$tokens, "\n";
+                $lineIndex++;
+                $isMultiLine = 1;
+
+                if ($lineIndex >= scalar @lines)
+                    {  last;  };
+
+                $line = $lines[$lineIndex];
+                };
+
+            if ($lineRemainder !~ /^[ \t]*$/)
+                {
+                # If there was something past the closing symbol this wasn't an acceptable comment.
+
+                if ($isMultiLine)
+                    {  $self->TokenizeLine($lineRemainder);  }
+                else
+                    {
+                    # We go back to the original line if it wasn't a multiline comment because we want the comment to stay in the
+                    # code.  Otherwise the /*@out@*/ from the example would be removed.
+                    $self->TokenizeLine($lines[$lineIndex]);
+                    };
+
+                @commentLines = ( );
+                }
+            else
+                {
+                push @$tokens, "\n";
+                };
+
+            $lineIndex++;
+            }
+
+
+        # Otherwise just add it to the code.
+
+        else
+            {
+            $self->TokenizeLine($line);
+            $lineIndex++;
+            };
+
+
+        # If there were comments, send them to Parser->OnComment().
+
+        if (scalar @commentLines)
+            {
+            NaturalDocs::Parser->OnComment(\@commentLines, $commentLineNumber, $isJavaDoc);
+            @commentLines = ( );
+            $isJavaDoc = undef;
+            };
+
+        # $lineIndex was incremented by the individual code paths above.
+
+        };  # while ($lineIndex < scalar @lines)
+    };
+
+
+#
+#   Function: PreprocessFile
+#
+#   An overridable function if you'd like to preprocess the file before it goes into <ParseForCommentsAndTokens()>.
+#
+#   Parameters:
+#
+#       lines - An arrayref to the file's lines.  Each line has its line break stripped off, but is otherwise untouched.
+#
+sub PreprocessFile #(lines)
+    {
+    };
+
+
+#
+#   Function: TokenizeLine
+#
+#   Converts the passed line to tokens as described in <ParseForCommentsAndTokens> and adds them to <Tokens()>.  Also
+#   adds a line break token after it.
+#
+sub TokenizeLine #(line)
+    {
+    my ($self, $line) = @_;
+    push @{$self->Tokens()}, $line =~ /(\w+|[ \t]+|.)/g, "\n";
+    };
+
+
+#
+#   Function: TryToSkipString
+#
+#   If the position is on a string delimiter, moves the position to the token following the closing delimiter, or past the end of the
+#   tokens if there is none.  Assumes all other characters are allowed in the string, the delimiter itself is allowed if it's preceded by
+#   a backslash, and line breaks are allowed in the string.
+#
+#   Parameters:
+#
+#       indexRef - A reference to the position's index into <Tokens()>.
+#       lineNumberRef - A reference to the position's line number.
+#       openingDelimiter - The opening string delimiter, such as a quote or an apostrophe.
+#       closingDelimiter - The closing string delimiter, if different.  If not defined, assumes the same as openingDelimiter.
+#       startContentIndexRef - A reference to a variable in which to store the index of the first token of the string's content.
+#                                         May be undef.
+#       endContentIndexRef - A reference to a variable in which to store the index of the end of the string's content, which is one
+#                                        past the last index of content.  May be undef.
+#
+#   Returns:
+#
+#       Whether the position was on the passed delimiter or not.  The index, line number, and content index ref variables will be
+#       updated only if true.
+#
+sub TryToSkipString #(indexRef, lineNumberRef, openingDelimiter, closingDelimiter, startContentIndexRef, endContentIndexRef)
+    {
+    my ($self, $index, $lineNumber, $openingDelimiter, $closingDelimiter, $startContentIndexRef, $endContentIndexRef) = @_;
+    my $tokens = $self->Tokens();
+
+    if (!defined $closingDelimiter)
+        {  $closingDelimiter = $openingDelimiter;  };
+
+    if ($tokens->[$$index] ne $openingDelimiter)
+        {  return undef;  };
+
+
+    $$index++;
+    if (defined $startContentIndexRef)
+        {  $$startContentIndexRef = $$index;  };
+
+    while ($$index < scalar @$tokens)
+        {
+        if ($tokens->[$$index] eq "\\")
+            {
+            # Skip the token after it.
+            $$index += 2;
+            }
+        elsif ($tokens->[$$index] eq "\n")
+            {
+            $$lineNumber++;
+            $$index++;
+            }
+        elsif ($tokens->[$$index] eq $closingDelimiter)
+            {
+            if (defined $endContentIndexRef)
+                {  $$endContentIndexRef = $$index;  };
+
+            $$index++;
+            last;
+            }
+        else
+            {
+            $$index++;
+            };
+        };
+
+    if ($$index >= scalar @$tokens && defined $endContentIndexRef)
+        {  $$endContentIndexRef = scalar @$tokens;  };
+
+    return 1;
+    };
+
+
+#
+#   Function: SkipRestOfLine
+#
+#   Moves the position to the token following the next line break, or past the end of the tokens array if there is none.  Useful for
+#   line comments.
+#
+#   Note that it skips blindly.  It assumes there cannot be anything of interest, such as a string delimiter, between the position
+#   and the end of the line.
+#
+#   Parameters:
+#
+#       indexRef - A reference to the position's index into <Tokens()>.
+#       lineNumberRef - A reference to the position's line number.
+
+sub SkipRestOfLine #(indexRef, lineNumberRef)
+    {
+    my ($self, $index, $lineNumber) = @_;
+    my $tokens = $self->Tokens();
+
+    while ($$index < scalar @$tokens)
+        {
+        if ($tokens->[$$index] eq "\n")
+            {
+            $$lineNumber++;
+            $$index++;
+            last;
+            }
+        else
+            {
+            $$index++;
+            };
+        };
+    };
+
+
+#
+#   Function: SkipUntilAfter
+#
+#   Moves the position to the token following the next occurance of a particular token sequence, or past the end of the tokens
+#   array if it never occurs.  Useful for multiline comments.
+#
+#   Note that it skips blindly.  It assumes there cannot be anything of interest, such as a string delimiter, between the position
+#   and the end of the line.
+#
+#   Parameters:
+#
+#       indexRef - A reference to the position's index.
+#       lineNumberRef - A reference to the position's line number.
+#       token - A token that must be matched.  Can be specified multiple times to match a sequence of tokens.
+#
+sub SkipUntilAfter #(indexRef, lineNumberRef, token, token, ...)
+    {
+    my ($self, $index, $lineNumber, @target) = @_;
+    my $tokens = $self->Tokens();
+
+    while ($$index < scalar @$tokens)
+        {
+        if ($tokens->[$$index] eq $target[0] && ($$index + scalar @target) <= scalar @$tokens)
+            {
+            my $match = 1;
+
+            for (my $i = 1; $i < scalar @target; $i++)
+                {
+                if ($tokens->[$$index+$i] ne $target[$i])
+                    {
+                    $match = 0;
+                    last;
+                    };
+                };
+
+            if ($match)
+                {
+                $$index += scalar @target;
+                return;
+                };
+            };
+
+        if ($tokens->[$$index] eq "\n")
+            {
+            $$lineNumber++;
+            $$index++;
+            }
+        else
+            {
+            $$index++;
+            };
+        };
+    };
+
+
+#
+#   Function: IsFirstLineToken
+#
+#   Returns whether the position is at the first token of a line, not including whitespace.
+#
+#   Parameters:
+#
+#       index - The index of the position.
+#
+sub IsFirstLineToken #(index)
+    {
+    my ($self, $index) = @_;
+    my $tokens = $self->Tokens();
+
+    if ($index == 0)
+        {  return 1;  };
+
+    $index--;
+
+    if ($tokens->[$index] =~ /^[ \t]/)
+        {  $index--;  };
+
+    if ($index <= 0 || $tokens->[$index] eq "\n")
+        {  return 1;  }
+    else
+        {  return undef;  };
+    };
+
+
+#
+#   Function: IsLastLineToken
+#
+#   Returns whether the position is at the last token of a line, not including whitespace.
+#
+#   Parameters:
+#
+#       index - The index of the position.
+#
+sub IsLastLineToken #(index)
+    {
+    my ($self, $index) = @_;
+    my $tokens = $self->Tokens();
+
+    do
+        {  $index++;  }
+    while ($index < scalar @$tokens && $tokens->[$index] =~ /^[ \t]/);
+
+    if ($index >= scalar @$tokens || $tokens->[$index] eq "\n")
+        {  return 1;  }
+    else
+        {  return undef;  };
+    };
+
+
+#
+#   Function: IsAtSequence
+#
+#   Returns whether the position is at a sequence of tokens.
+#
+#   Parameters:
+#
+#       index - The index of the position.
+#       token - A token to match.  Specify multiple times to specify the sequence.
+#
+sub IsAtSequence #(index, token, token, token ...)
+    {
+    my ($self, $index, @target) = @_;
+    my $tokens = $self->Tokens();
+
+    if ($index + scalar @target > scalar @$tokens)
+        {  return undef;  };
+
+    for (my $i = 0; $i < scalar @target; $i++)
+        {
+        if ($tokens->[$index + $i] ne $target[$i])
+            {  return undef;  };
+        };
+
+    return 1;
+    };
+
+
+#
+#   Function: IsBackslashed
+#
+#   Returns whether the position is after a backslash.
+#
+#   Parameters:
+#
+#       index - The index of the postition.
+#
+sub IsBackslashed #(index)
+    {
+    my ($self, $index) = @_;
+    my $tokens = $self->Tokens();
+
+    if ($index > 0 && $tokens->[$index - 1] eq "\\")
+        {  return 1;  }
+    else
+        {  return undef;  };
+    };
+
+
+
+###############################################################################
+#
+#   Group: Scope Functions
+#
+#   These functions provide a nice scope stack implementation for language-specific parsers to use.  The default implementation
+#   makes the following assumptions.
+#
+#   - Packages completely replace one another, rather than concatenating.  You need to concatenate manually if that's the
+#     behavior.
+#
+#   - Packages inherit, so if a scope level doesn't set its own, the package is the same as the parent scope's.
+#
+
+
+#
+#   Function: ClearScopeStack
+#
+#   Clears the scope stack for a new file.  Not necessary if you call <ParseForCommentsAndTokens()>.
+#
+sub ClearScopeStack
+    {
+    my ($self) = @_;
+    $self->[SCOPE_STACK] = [ NaturalDocs::Languages::Advanced::Scope->New(undef, undef) ];
+    $self->[SCOPE_RECORD] = [ NaturalDocs::Languages::Advanced::ScopeChange->New(undef, 1) ];
+    };
+
+
+#
+#   Function: StartScope
+#
+#   Records a new scope level.
+#
+#   Parameters:
+#
+#       closingSymbol - The closing symbol of the scope.
+#       lineNumber - The line number where the scope begins.
+#       package - The package <SymbolString> of the scope.  Undef means no change.
+#
+sub StartScope #(closingSymbol, lineNumber, package)
+    {
+    my ($self, $closingSymbol, $lineNumber, $package) = @_;
+
+    push @{$self->[SCOPE_STACK]},
+            NaturalDocs::Languages::Advanced::Scope->New($closingSymbol, $package, $self->CurrentUsing());
+
+    $self->AddToScopeRecord($self->CurrentScope(), $lineNumber);
+    };
+
+
+#
+#   Function: EndScope
+#
+#   Records the end of the current scope level.  Note that this is blind; you need to manually check <ClosingScopeSymbol()> if
+#   you need to determine if it is correct to do so.
+#
+#   Parameters:
+#
+#       lineNumber - The line number where the scope ends.
+#
+sub EndScope #(lineNumber)
+    {
+    my ($self, $lineNumber) = @_;
+
+    if (scalar @{$self->[SCOPE_STACK]} > 1)
+        {  pop @{$self->[SCOPE_STACK]};  };
+
+    $self->AddToScopeRecord($self->CurrentScope(), $lineNumber);
+    };
+
+
+#
+#   Function: ClosingScopeSymbol
+#
+#   Returns the symbol that ends the current scope level, or undef if we are at the top level.
+#
+sub ClosingScopeSymbol
+    {
+    my ($self) = @_;
+    return $self->[SCOPE_STACK]->[-1]->ClosingSymbol();
+    };
+
+
+#
+#   Function: CurrentScope
+#
+#   Returns the current calculated scope, or undef if global.  The default implementation just returns <CurrentPackage()>.  This
+#   is a separate function because C++ may need to track namespaces and classes separately, and so the current scope would
+#   be a concatenation of them.
+#
+sub CurrentScope
+    {
+    return $_[0]->CurrentPackage();
+    };
+
+
+#
+#   Function: CurrentPackage
+#
+#   Returns the current calculated package or class, or undef if none.
+#
+sub CurrentPackage
+    {
+    my ($self) = @_;
+
+    my $package;
+
+    for (my $index = scalar @{$self->[SCOPE_STACK]} - 1; $index >= 0 && !defined $package; $index--)
+        {
+        $package = $self->[SCOPE_STACK]->[$index]->Package();
+        };
+
+    return $package;
+    };
+
+
+#
+#   Function: SetPackage
+#
+#   Sets the package for the current scope level.
+#
+#   Parameters:
+#
+#       package - The new package <SymbolString>.
+#       lineNumber - The line number the new package starts on.
+#
+sub SetPackage #(package, lineNumber)
+    {
+    my ($self, $package, $lineNumber) = @_;
+    $self->[SCOPE_STACK]->[-1]->SetPackage($package);
+
+    $self->AddToScopeRecord($self->CurrentScope(), $lineNumber);
+    };
+
+
+#
+#   Function: CurrentUsing
+#
+#   Returns the current calculated arrayref of <SymbolStrings> from Using statements, or undef if none.
+#
+sub CurrentUsing
+    {
+    my ($self) = @_;
+    return $self->[SCOPE_STACK]->[-1]->Using();
+    };
+
+
+#
+#   Function: AddUsing
+#
+#   Adds a Using <SymbolString> to the current scope.
+#
+sub AddUsing #(using)
+    {
+    my ($self, $using) = @_;
+    $self->[SCOPE_STACK]->[-1]->AddUsing($using);
+    };
+
+
+
+###############################################################################
+# Group: Support Functions
+
+
+#
+#   Function: AddToScopeRecord
+#
+#   Adds a change to the scope record, condensing unnecessary entries.
+#
+#   Parameters:
+#
+#       newScope - What the scope <SymbolString> changed to.
+#       lineNumber - Where the scope changed.
+#
+sub AddToScopeRecord #(newScope, lineNumber)
+    {
+    my ($self, $scope, $lineNumber) = @_;
+    my $scopeRecord = $self->ScopeRecord();
+
+    if ($scope ne $scopeRecord->[-1]->Scope())
+        {
+        if ($scopeRecord->[-1]->LineNumber() == $lineNumber)
+            {  $scopeRecord->[-1]->SetScope($scope);  }
+        else
+            {  push @$scopeRecord, NaturalDocs::Languages::Advanced::ScopeChange->New($scope, $lineNumber);  };
+        };
+    };
+
+
+#
+#   Function: CreateString
+#
+#   Converts the specified tokens into a string and returns it.
+#
+#   Parameters:
+#
+#       startIndex - The starting index to convert.
+#       endIndex - The ending index, which is *not inclusive*.
+#
+#   Returns:
+#
+#       The string.
+#
+sub CreateString #(startIndex, endIndex)
+    {
+    my ($self, $startIndex, $endIndex) = @_;
+    my $tokens = $self->Tokens();
+
+    my $string;
+
+    while ($startIndex < $endIndex && $startIndex < scalar @$tokens)
+        {
+        $string .= $tokens->[$startIndex];
+        $startIndex++;
+        };
+
+    return $string;
+    };
+
+
+1;

Added: trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Languages/Base.pm
===================================================================
--- trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Languages/Base.pm	                        (rev 0)
+++ trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Languages/Base.pm	2009-07-01 15:30:00 UTC (rev 4275)
@@ -0,0 +1,832 @@
+###############################################################################
+#
+#   Class: NaturalDocs::Languages::Base
+#
+###############################################################################
+#
+#   A base class for all programming language parsers.
+#
+###############################################################################
+
+# 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::Languages::Base;
+
+use NaturalDocs::DefineMembers 'NAME', 'Name()',
+                                                 'EXTENSIONS', 'Extensions()', 'SetExtensions() duparrayref',
+                                                 'SHEBANG_STRINGS', 'ShebangStrings()', 'SetShebangStrings() duparrayref',
+                                                 'IGNORED_PREFIXES',
+                                                 'ENUM_VALUES';
+
+use base 'Exporter';
+our @EXPORT = ('ENUM_GLOBAL', 'ENUM_UNDER_TYPE', 'ENUM_UNDER_PARENT');
+
+
+#
+#   Constants: EnumValuesType
+#
+#   How enum values are handled in the language.
+#
+#   ENUM_GLOBAL - Values are always global and thus 'value'.
+#   ENUM_UNDER_TYPE - Values are under the type in the hierarchy, and thus 'package.enum.value'.
+#   ENUM_UNDER_PARENT - Values are under the parent in the hierarchy, putting them on the same level as the enum itself.  Thus
+#                                       'package.value'.
+#
+use constant ENUM_GLOBAL => 1;
+use constant ENUM_UNDER_TYPE => 2;
+use constant ENUM_UNDER_PARENT => 3;
+
+
+#
+#   Handle: SOURCEFILEHANDLE
+#
+#   The handle of the source file currently being parsed.
+#
+
+
+#
+#   Function: New
+#
+#   Creates and returns a new object.
+#
+#   Parameters:
+#
+#       name - The name of the language.
+#
+sub New #(name)
+    {
+    my ($selfPackage, $name) = @_;
+
+    my $object = [ ];
+
+    $object->[NAME] = $name;
+
+    bless $object, $selfPackage;
+    return $object;
+    };
+
+
+#
+#   Functions: Members
+#
+#   Name - Returns the language's name.
+#   Extensions - Returns an arrayref of the language's file extensions, or undef if none.
+#   SetExtensions - Replaces the arrayref of the language's file extensions.
+#   ShebangStrings - Returns an arrayref of the language's shebang strings, or undef if none.
+#   SetShebangStrings - Replaces the arrayref of the language's shebang strings.
+#
+
+#
+#   Function: PackageSeparator
+#   Returns the language's package separator string.
+#
+sub PackageSeparator
+    {  return '.';  };
+
+#
+#   Function: PackageSeparatorWasSet
+#   Returns whether the language's package separator string was ever changed from the default.
+#
+sub PackageSeparatorWasSet
+    {  return 0;  };
+
+
+#
+#   Function: EnumValues
+#   Returns the <EnumValuesType> that describes how the language handles enums.
+#
+sub EnumValues
+    {  return ENUM_GLOBAL;  };
+
+
+#
+#   Function: IgnoredPrefixesFor
+#
+#   Returns an arrayref of ignored prefixes for the passed <TopicType>, or undef if none.  The array is sorted so that the longest
+#   prefixes are first.
+#
+sub IgnoredPrefixesFor #(type)
+    {
+    my ($self, $type) = @_;
+
+    if (defined $self->[IGNORED_PREFIXES])
+        {  return $self->[IGNORED_PREFIXES]->{$type};  }
+    else
+        {  return undef;  };
+    };
+
+
+#
+#   Function: SetIgnoredPrefixesFor
+#
+#   Replaces the arrayref of ignored prefixes for the passed <TopicType>.
+#
+sub SetIgnoredPrefixesFor #(type, prefixes)
+    {
+    my ($self, $type, $prefixesRef) = @_;
+
+    if (!defined $self->[IGNORED_PREFIXES])
+        {  $self->[IGNORED_PREFIXES] = { };  };
+
+    if (!defined $prefixesRef)
+        {  delete $self->[IGNORED_PREFIXES]->{$type};  }
+    else
+        {
+        my $prefixes = [ @$prefixesRef ];
+
+        # Sort prefixes to be longest to shortest.
+        @$prefixes = sort { length $b <=> length $a } @$prefixes;
+
+        $self->[IGNORED_PREFIXES]->{$type} = $prefixes;
+        };
+    };
+
+
+#
+#   Function: HasIgnoredPrefixes
+#
+#   Returns whether the language has any ignored prefixes at all.
+#
+sub HasIgnoredPrefixes
+    {  return defined $_[0]->[IGNORED_PREFIXES];  };
+
+
+#
+#   Function: CopyIgnoredPrefixesOf
+#
+#   Copies all the ignored prefix settings of the passed <NaturalDocs::Languages::Base> object.
+#
+sub CopyIgnoredPrefixesOf #(language)
+    {
+    my ($self, $language) = @_;
+
+    if ($language->HasIgnoredPrefixes())
+        {
+        $self->[IGNORED_PREFIXES] = { };
+
+        while (my ($topicType, $prefixes) = each %{$language->[IGNORED_PREFIXES]})
+            {
+            $self->[IGNORED_PREFIXES]->{$topicType} = [ @$prefixes ];
+            };
+        };
+    };
+
+
+
+###############################################################################
+# Group: Parsing Functions
+
+
+#
+#   Function: ParseFile
+#
+#   Parses the passed source file, sending comments acceptable for documentation to <NaturalDocs::Parser->OnComment()>.
+#   This *must* be defined by a subclass.
+#
+#   Parameters:
+#
+#       sourceFile - The <FileName> of the source file to parse.
+#       topicList - A reference to the list of <NaturalDocs::Parser::ParsedTopics> being built by the file.
+#
+#   Returns:
+#
+#       The array ( autoTopics, scopeRecord ).
+#
+#       autoTopics - An arrayref of automatically generated <NaturalDocs::Parser::ParsedTopics> from the file, or undef if none.
+#       scopeRecord - An arrayref of <NaturalDocs::Languages::Advanced::ScopeChanges>, or undef if none.
+#
+
+
+#
+#   Function: ParsePrototype
+#
+#   Parses the prototype and returns it as a <NaturalDocs::Languages::Prototype> object.
+#
+#   Parameters:
+#
+#       type - The <TopicType>.
+#       prototype - The text prototype.
+#
+#   Returns:
+#
+#       A <NaturalDocs::Languages::Prototype> object.
+#
+sub ParsePrototype #(type, prototype)
+    {
+    my ($self, $type, $prototype) = @_;
+
+    my $isClass = NaturalDocs::Topics->TypeInfo($type)->ClassHierarchy();
+
+    if ($prototype !~ /\(.*[^ ].*\)/ && (!$isClass || $prototype !~ /\{.*[^ ].*\}/))
+        {
+        my $object = NaturalDocs::Languages::Prototype->New($prototype);
+        return $object;
+        };
+
+
+    # Parse the parameters out of the prototype.
+
+    my @tokens = $prototype =~ /([^\(\)\[\]\{\}\<\>\'\"\,\;]+|.)/g;
+
+    my $parameter;
+    my @parameterLines;
+
+    my @symbolStack;
+    my $finishedParameters;
+
+    my ($beforeParameters, $afterParameters);
+
+    foreach my $token (@tokens)
+        {
+        if ($finishedParameters)
+            {  $afterParameters .= $token;  }
+
+        elsif ($symbolStack[-1] eq '\'' || $symbolStack[-1] eq '"')
+            {
+            if ($symbolStack[0] eq '(' || ($isClass && $symbolStack[0] eq '{'))
+                {  $parameter .= $token;  }
+            else
+                {  $beforeParameters .= $token;  };
+
+            if ($token eq $symbolStack[-1])
+                {  pop @symbolStack;  };
+            }
+
+        elsif ($token =~ /^[\(\[\{\<\'\"]$/)
+            {
+            if ($symbolStack[0] eq '(' || ($isClass && $symbolStack[0] eq '{'))
+                {  $parameter .= $token;   }
+            else
+                {  $beforeParameters .= $token;  };
+
+            push @symbolStack, $token;
+            }
+
+        elsif ( ($token eq ')' && $symbolStack[-1] eq '(') ||
+                 ($token eq ']' && $symbolStack[-1] eq '[') ||
+                 ($token eq '}' && $symbolStack[-1] eq '{') ||
+                 ($token eq '>' && $symbolStack[-1] eq '<') )
+            {
+            if ($symbolStack[0] eq '(')
+                {
+                if ($token eq ')' && scalar @symbolStack == 1)
+                    {
+                    if ($parameter ne ' ')
+                        {  push @parameterLines, $parameter;  };
+
+                    $finishedParameters = 1;
+                    $afterParameters .= $token;
+                    }
+                else
+                    {  $parameter .= $token;  };
+                }
+            elsif ($isClass && $symbolStack[0] eq '{')
+                {
+                if ($token eq '}' && scalar @symbolStack == 1)
+                    {
+                    if ($parameter ne ' ')
+                        {  push @parameterLines, $parameter;  };
+
+                    $finishedParameters = 1;
+                    $afterParameters .= $token;
+                    }
+                else
+                    {  $parameter .= $token;  };
+                }
+            else
+                {
+                $beforeParameters .= $token;
+                };
+
+            pop @symbolStack;
+            }
+
+        elsif ($token eq ',' || $token eq ';')
+            {
+            if ($symbolStack[0] eq '(' || ($isClass && $symbolStack[0] eq '{'))
+                {
+                if (scalar @symbolStack == 1)
+                    {
+                    push @parameterLines, $parameter . $token;
+                    $parameter = undef;
+                    }
+                else
+                    {
+                    $parameter .= $token;
+                    };
+                }
+            else
+                {
+                $beforeParameters .= $token;
+                };
+            }
+
+        else
+            {
+            if ($symbolStack[0] eq '(' || ($isClass && $symbolStack[0] eq '{'))
+                {  $parameter .= $token;  }
+            else
+                {  $beforeParameters .= $token;  };
+            };
+        };
+
+    foreach my $part (\$beforeParameters, \$afterParameters)
+        {
+        $$part =~ s/^ //;
+        $$part =~ s/ $//;
+        };
+
+    my $prototypeObject = NaturalDocs::Languages::Prototype->New($beforeParameters, $afterParameters);
+
+
+    # Parse the actual parameters.
+
+    foreach my $parameterLine (@parameterLines)
+        {
+        $prototypeObject->AddParameter( $self->ParseParameterLine($parameterLine) );
+        };
+
+    return $prototypeObject;
+    };
+
+
+#
+#   Function: ParseParameterLine
+#
+#   Parses a prototype parameter line and returns it as a <NaturalDocs::Languages::Prototype::Parameter> object.
+#
+#   This vesion assumes a C++ style line.  If you need a Pascal style line, override this function to forward to
+#   <ParsePascalParameterLine()>.
+#
+#   > Function(parameter, type parameter, type parameter = value);
+#
+sub ParseParameterLine #(line)
+    {
+    my ($self, $line) = @_;
+
+    $line =~ s/^ //;
+    $line =~ s/ $//;
+
+    my @tokens = $line =~ /([^ \(\)\{\}\[\]\<\>\'\"\=]+|.)/g;
+
+    my @symbolStack;
+    my @parameterWords = ( undef );
+    my ($defaultValue, $defaultValuePrefix, $inDefaultValue);
+
+    foreach my $token (@tokens)
+        {
+        if ($inDefaultValue)
+            {  $defaultValue .= $token;  }
+
+        elsif ($symbolStack[-1] eq '\'' || $symbolStack[-1] eq '"')
+            {
+            $parameterWords[-1] .= $token;
+
+            if ($token eq $symbolStack[-1])
+                {  pop @symbolStack;  };
+            }
+
+        elsif ($token =~ /^[\(\[\{\<\'\"]$/)
+            {
+            push @symbolStack, $token;
+            $parameterWords[-1] .= $token;
+            }
+
+        elsif ( ($token eq ')' && $symbolStack[-1] eq '(') ||
+                 ($token eq ']' && $symbolStack[-1] eq '[') ||
+                 ($token eq '}' && $symbolStack[-1] eq '{') ||
+                 ($token eq '>' && $symbolStack[-1] eq '<') )
+            {
+            pop @symbolStack;
+            $parameterWords[-1] .= $token;
+            }
+
+        elsif ($token eq ' ')
+            {
+            if (!scalar @symbolStack)
+                {  push @parameterWords, undef;  }
+            else
+                {  $parameterWords[-1] .= $token;  };
+            }
+
+        elsif ($token eq '=')
+            {
+            if (!scalar @symbolStack)
+                {
+                $defaultValuePrefix = $token;
+                $inDefaultValue = 1;
+                }
+            else
+                {  $parameterWords[-1] .= $token;  };
+            }
+
+        else
+            {
+            $parameterWords[-1] .= $token;
+            };
+        };
+
+    my ($name, $namePrefix, $type, $typePrefix);
+
+    if (!$parameterWords[-1])
+        {  pop @parameterWords;  };
+
+    $name = pop @parameterWords;
+
+    if ($parameterWords[-1]=~ /([\*\&]+)$/)
+        {
+        $namePrefix = $1;
+        $parameterWords[-1] = substr($parameterWords[-1], 0, 0 - length($namePrefix));
+        $parameterWords[-1] =~ s/ $//;
+
+        if (!$parameterWords[-1])
+            {  pop @parameterWords;  };
+        }
+    elsif ($name =~ /^([\*\&]+)/)
+        {
+        $namePrefix = $1;
+        $name = substr($name, length($namePrefix));
+        $name =~ s/^ //;
+        };
+
+    $type = pop @parameterWords;
+    $typePrefix = join(' ', @parameterWords);
+
+    if ($typePrefix)
+        {  $typePrefix .= ' ';  };
+
+    if ($type =~ /^([a-z0-9_\:\.]+(?:\.|\:\:))[a-z0-9_]/i)
+        {
+        my $attachedTypePrefix = $1;
+
+        $typePrefix .= $attachedTypePrefix;
+        $type = substr($type, length($attachedTypePrefix));
+        };
+
+    $defaultValue =~ s/ $//;
+
+    return NaturalDocs::Languages::Prototype::Parameter->New($type, $typePrefix, $name, $namePrefix,
+                                                                                             $defaultValue, $defaultValuePrefix);
+    };
+
+
+#
+#   Function: ParsePascalParameterLine
+#
+#   Parses a Pascal-like prototype parameter line and returns it as a <NaturalDocs::Languages::Prototype::Parameter> object.
+#   Pascal lines are as follows:
+#
+#   > Function (name: type; name, name: type := value)
+#
+#   Also supports ActionScript lines
+#
+#   > Function (name: type, name, name: type = value)
+#
+sub ParsePascalParameterLine #(line)
+    {
+    my ($self, $line) = @_;
+
+    $line =~ s/^ //;
+    $line =~ s/ $//;
+
+    my @tokens = $line =~ /([^\(\)\{\}\[\]\<\>\'\"\=\:]+|\:\=|.)/g;
+    my ($type, $name, $defaultValue, $defaultValuePrefix, $afterName, $afterDefaultValue);
+    my @symbolStack;
+
+    foreach my $token (@tokens)
+        {
+        if ($afterDefaultValue)
+            {  $defaultValue .= $token;  }
+
+        elsif ($symbolStack[-1] eq '\'' || $symbolStack[-1] eq '"')
+            {
+            if ($afterName)
+                {  $type .= $token;  }
+            else
+                {  $name .= $token;  };
+
+            if ($token eq $symbolStack[-1])
+                {  pop @symbolStack;  };
+            }
+
+        elsif ($token =~ /^[\(\[\{\<\'\"]$/)
+            {
+            push @symbolStack, $token;
+
+            if ($afterName)
+                {  $type .= $token;  }
+            else
+                {  $name .= $token;  };
+            }
+
+        elsif ( ($token eq ')' && $symbolStack[-1] eq '(') ||
+                 ($token eq ']' && $symbolStack[-1] eq '[') ||
+                 ($token eq '}' && $symbolStack[-1] eq '{') ||
+                 ($token eq '>' && $symbolStack[-1] eq '<') )
+            {
+            pop @symbolStack;
+
+            if ($afterName)
+                {  $type .= $token;  }
+            else
+                {  $name .= $token;  };
+            }
+
+        elsif ($afterName)
+            {
+            if (($token eq ':=' || $token eq '=') && !scalar @symbolStack)
+                {
+                $defaultValuePrefix = $token;
+                $afterDefaultValue = 1;
+                }
+            else
+                {  $type .= $token;  };
+            }
+
+        elsif ($token eq ':' && !scalar @symbolStack)
+            {
+            $name .= $token;
+            $afterName = 1;
+            }
+
+        else
+            {  $name .= $token;  };
+        };
+
+    foreach my $part (\$type, \$name, \$defaultValue)
+        {
+        $$part =~ s/^ //;
+        $$part =~ s/ $//;
+        };
+
+    return NaturalDocs::Languages::Prototype::Parameter->New($type, undef, $name, undef, $defaultValue, $defaultValuePrefix);
+    };
+
+
+#
+#   Function: TypeBeforeParameter
+#
+#   Returns whether the type appears before the parameter in prototypes.
+#
+#   For example, it does in C++
+#   > void Function (int a, int b)
+#
+#   but does not in Pascal
+#   > function Function (a: int; b, c: int)
+#
+sub TypeBeforeParameter
+    {
+    return 1;
+    };
+
+
+
+#
+#   Function: IgnoredPrefixLength
+#
+#   Returns the length of the prefix that should be ignored in the index, or zero if none.
+#
+#   Parameters:
+#
+#       name - The name of the symbol.
+#       type  - The symbol's <TopicType>.
+#
+#   Returns:
+#
+#       The length of the prefix to ignore, or zero if none.
+#
+sub IgnoredPrefixLength #(name, type)
+    {
+    my ($self, $name, $type) = @_;
+
+    foreach my $prefixes ($self->IgnoredPrefixesFor($type), $self->IgnoredPrefixesFor(::TOPIC_GENERAL()))
+        {
+        if (defined $prefixes)
+            {
+            foreach my $prefix (@$prefixes)
+                {
+                if (substr($name, 0, length($prefix)) eq $prefix)
+                    {  return length($prefix);  };
+                };
+            };
+        };
+
+    return 0;
+    };
+
+
+
+###############################################################################
+# Group: Support Functions
+
+
+#
+#   Function: StripOpeningSymbols
+#
+#   Determines if the line starts with any of the passed symbols, and if so, replaces it with spaces.  This only happens
+#   if the only thing before it on the line is whitespace.
+#
+#   Parameters:
+#
+#       lineRef - A reference to the line to check.
+#       symbols - An arrayref of the symbols to check for.
+#
+#   Returns:
+#
+#       If the line starts with any of the passed comment symbols, it will replace it in the line with spaces and return the symbol.
+#       If the line doesn't, it will leave the line alone and return undef.
+#
+sub StripOpeningSymbols #(lineRef, symbols)
+    {
+    my ($self, $lineRef, $symbols) = @_;
+
+    if (!defined $symbols)
+        {  return undef;  };
+
+    my ($index, $symbol) = ::FindFirstSymbol($$lineRef, $symbols);
+
+    if ($index != -1 && substr($$lineRef, 0, $index) =~ /^[ \t]*$/)
+        {
+        return substr($$lineRef, $index, length($symbol), ' ' x length($symbol));
+        };
+
+    return undef;
+    };
+
+
+#
+#   Function: StripOpeningJavaDocSymbols
+#
+#   Determines if the line starts with any of the passed symbols, and if so, replaces it with spaces.  This only happens
+#   if the only thing before it on the line is whitespace and the next character after it is whitespace or the end of the line.
+#
+#   Parameters:
+#
+#       lineRef - A reference to the line to check.
+#       symbols - An arrayref of the symbols to check for.
+#
+#   Returns:
+#
+#       If the line starts with any of the passed comment symbols, it will replace it in the line with spaces and return the symbol.
+#       If the line doesn't, it will leave the line alone and return undef.
+#
+sub StripOpeningJavaDocSymbols #(lineRef, symbols)
+    {
+    my ($self, $lineRef, $symbols) = @_;
+
+    if (!defined $symbols)
+        {  return undef;  };
+
+    my ($index, $symbol) = ::FindFirstSymbol($$lineRef, $symbols);
+
+    if ($index != -1 && substr($$lineRef, 0, $index) =~ /^[ \t]*$/ && substr($$lineRef, $index + length($symbol), 1) =~ /^[ \t]?$/)
+        {
+        return substr($$lineRef, $index, length($symbol), ' ' x length($symbol));
+        };
+
+    return undef;
+    };
+
+
+#
+#   Function: StripOpeningBlockSymbols
+#
+#   Determines if the line starts with any of the opening symbols in the passed symbol pairs, and if so, replaces it with spaces.
+#   This only happens if the only thing before it on the line is whitespace.
+#
+#   Parameters:
+#
+#       lineRef - A reference to the line to check.
+#       symbolPairs - An arrayref of the symbol pairs to check for.  Pairs are specified as two consecutive array entries, with the
+#                            opening symbol first.
+#
+#   Returns:
+#
+#       If the line starts with any of the opening symbols, it will replace it in the line with spaces and return the closing symbol.
+#       If the line doesn't, it will leave the line alone and return undef.
+#
+sub StripOpeningBlockSymbols #(lineRef, symbolPairs)
+    {
+    my ($self, $lineRef, $symbolPairs) = @_;
+
+    if (!defined $symbolPairs)
+        {  return undef;  };
+
+    for (my $i = 0; $i < scalar @$symbolPairs; $i += 2)
+        {
+        my $index = index($$lineRef, $symbolPairs->[$i]);
+
+        if ($index != -1 && substr($$lineRef, 0, $index) =~ /^[ \t]*$/)
+            {
+            substr($$lineRef, $index, length($symbolPairs->[$i]), ' ' x length($symbolPairs->[$i]));
+            return $symbolPairs->[$i + 1];
+            };
+        };
+
+    return undef;
+    };
+
+
+#
+#   Function: StripOpeningJavaDocBlockSymbols
+#
+#   Determines if the line starts with any of the opening symbols in the passed symbol pairs, and if so, replaces it with spaces.
+#   This only happens if the only thing before it on the line is whitespace and the next character is whitespace or the end of the line.
+#
+#   Parameters:
+#
+#       lineRef - A reference to the line to check.
+#       symbolPairs - An arrayref of the symbol pairs to check for.  Pairs are specified as two consecutive array entries, with the
+#                            opening symbol first.
+#
+#   Returns:
+#
+#       If the line starts with any of the opening symbols, it will replace it in the line with spaces and return the closing symbol.
+#       If the line doesn't, it will leave the line alone and return undef.
+#
+sub StripOpeningJavaDocBlockSymbols #(lineRef, symbolPairs)
+    {
+    my ($self, $lineRef, $symbolPairs) = @_;
+
+    if (!defined $symbolPairs)
+        {  return undef;  };
+
+    for (my $i = 0; $i < scalar @$symbolPairs; $i += 2)
+        {
+        my $index = index($$lineRef, $symbolPairs->[$i]);
+
+        if ($index != -1 && substr($$lineRef, 0, $index) =~ /^[ \t]*$/ &&
+            substr($$lineRef, $index + length($symbolPairs->[$i]), 1) =~ /^[ \t]?$/)
+            {
+            substr($$lineRef, $index, length($symbolPairs->[$i]), ' ' x length($symbolPairs->[$i]));
+            return $symbolPairs->[$i + 1];
+            };
+        };
+
+    return undef;
+    };
+
+
+#
+#   Function: StripClosingSymbol
+#
+#   Determines if the line contains a symbol, and if so, truncates it just before the symbol.
+#
+#   Parameters:
+#
+#       lineRef - A reference to the line to check.
+#       symbol - The symbol to check for.
+#
+#   Returns:
+#
+#       The remainder of the line, or undef if the symbol was not found.
+#
+sub StripClosingSymbol #(lineRef, symbol)
+    {
+    my ($self, $lineRef, $symbol) = @_;
+
+    my $index = index($$lineRef, $symbol);
+
+    if ($index != -1)
+        {
+        my $lineRemainder = substr($$lineRef, $index + length($symbol));
+        $$lineRef = substr($$lineRef, 0, $index);
+
+        return $lineRemainder;
+        }
+    else
+        {  return undef;  };
+    };
+
+
+#
+#   Function: NormalizePrototype
+#
+#   Normalizes a prototype.  Specifically, condenses spaces, tabs, and line breaks into single spaces and removes leading and
+#   trailing ones.
+#
+#   Parameters:
+#
+#       prototype - The original prototype string.
+#
+#   Returns:
+#
+#       The normalized prototype.
+#
+sub NormalizePrototype #(prototype)
+    {
+    my ($self, $prototype) = @_;
+
+    $prototype =~ tr/ \t\r\n/ /s;
+    $prototype =~ s/^ //;
+    $prototype =~ s/ $//;
+
+    return $prototype;
+    };
+
+
+1;

Added: trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Languages/CSharp.pm
===================================================================
--- trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Languages/CSharp.pm	                        (rev 0)
+++ trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Languages/CSharp.pm	2009-07-01 15:30:00 UTC (rev 4275)
@@ -0,0 +1,1484 @@
+###############################################################################
+#
+#   Class: NaturalDocs::Languages::CSharp
+#
+###############################################################################
+#
+#   A subclass to handle the language variations of C#.
+#
+#
+#   Topic: Language Support
+#
+#       Supported:
+#
+#       - Classes
+#       - Namespaces (no topic generated)
+#       - Functions
+#       - Constructors and Destructors
+#       - Properties
+#       - Indexers
+#       - Operators
+#       - Delegates
+#       - Variables
+#       - Constants
+#       - Events
+#       - Enums
+#
+#       Not supported yet:
+#
+#       - Autodocumenting enum members
+#       - Using alias
+#
+###############################################################################
+
+# 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::Languages::CSharp;
+
+use base 'NaturalDocs::Languages::Advanced';
+
+
+###############################################################################
+# Group: Package Variables
+
+#
+#   hash: classKeywords
+#   An existence hash of all the acceptable class keywords.  The keys are in all lowercase.
+#
+my %classKeywords = ( 'class' => 1,
+                                    'struct' => 1,
+                                    'interface' => 1 );
+
+#
+#   hash: classModifiers
+#   An existence hash of all the acceptable class modifiers.  The keys are in all lowercase.
+#
+my %classModifiers = ( 'new' => 1,
+                                   'public' => 1,
+                                   'protected' => 1,
+                                   'internal' => 1,
+                                   'private' => 1,
+                                   'abstract' => 1,
+                                   'sealed' => 1,
+                                   'unsafe' => 1,
+                                   'static' => 1 );
+
+#
+#   hash: functionModifiers
+#   An existence hash of all the acceptable function modifiers.  Also applies to properties.  Also encompasses those for operators
+#   and indexers, but have more than are valid for them.  The keys are in all lowercase.
+#
+my %functionModifiers = ( 'new' => 1,
+                                       'public' => 1,
+                                       'protected' => 1,
+                                       'internal' => 1,
+                                       'private' => 1,
+                                       'static' => 1,
+                                       'virtual' => 1,
+                                       'sealed' => 1,
+                                       'override' => 1,
+                                       'abstract' => 1,
+                                       'extern' => 1,
+                                       'unsafe' => 1 );
+
+#
+#   hash: variableModifiers
+#   An existence hash of all the acceptable variable modifiers.  The keys are in all lowercase.
+#
+my %variableModifiers = ( 'new' => 1,
+                                       'public' => 1,
+                                       'protected' => 1,
+                                       'internal' => 1,
+                                       'private' => 1,
+                                       'static' => 1,
+                                       'readonly' => 1,
+                                       'volatile' => 1,
+                                       'unsafe' => 1 );
+
+#
+#   hash: enumTypes
+#   An existence hash of all the possible enum types.  The keys are in all lowercase.
+#
+my %enumTypes = ( 'sbyte' => 1,
+                             'byte' => 1,
+                             'short' => 1,
+                             'ushort' => 1,
+                             'int' => 1,
+                             'uint' => 1,
+                             'long' => 1,
+                             'ulong' => 1 );
+
+#
+#   hash: impossibleTypeWords
+#   An existence hash of all the reserved words that cannot be in a type.  This includes 'enum' and all modifiers.  The keys are in
+#   all lowercase.
+#
+my %impossibleTypeWords = ( 'abstract' => 1, 'as' => 1, 'base' => 1, 'break' => 1, 'case' => 1, 'catch' => 1,
+                                              'checked' => 1, 'class' => 1, 'const' => 1, 'continue' => 1, 'default' => 1, 'delegate' => 1,
+                                              'do' => 1, 'else' => 1, 'enum' => 1, 'event' => 1, 'explicit' => 1, 'extern' => 1,
+                                              'false' => 1, 'finally' => 1, 'fixed' => 1, 'for' => 1, 'foreach' => 1, 'goto' => 1, 'if' => 1,
+                                              'implicit' => 1, 'in' => 1, 'interface' => 1, 'internal' => 1, 'is' => 1, 'lock' => 1,
+                                              'namespace' => 1, 'new' => 1, 'null' => 1, 'operator' => 1, 'out' => 1, 'override' => 1,
+                                              'params' => 1, 'private' => 1, 'protected' => 1, 'public' => 1, 'readonly' => 1, 'ref' => 1,
+                                              'return' => 1, 'sealed' => 1, 'sizeof' => 1, 'stackalloc' => 1, 'static' => 1,
+                                              'struct' => 1, 'switch' => 1, 'this' => 1, 'throw' => 1, 'true' => 1, 'try' => 1, 'typeof' => 1,
+                                              'unchecked' => 1, 'unsafe' => 1, 'using' => 1, 'virtual' => 1, 'volatile' => 1, 'while' => 1 );
+# Deleted from the list: object, string, bool, decimal, sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, void
+
+
+
+###############################################################################
+# Group: Interface Functions
+
+
+#
+#   Function: PackageSeparator
+#   Returns the package separator symbol.
+#
+sub PackageSeparator
+    {  return '.';  };
+
+
+#
+#   Function: EnumValues
+#   Returns the <EnumValuesType> that describes how the language handles enums.
+#
+sub EnumValues
+    {  return ::ENUM_UNDER_TYPE();  };
+
+
+#
+#   Function: ParseFile
+#
+#   Parses the passed source file, sending comments acceptable for documentation to <NaturalDocs::Parser->OnComment()>.
+#
+#   Parameters:
+#
+#       sourceFile - The <FileName> to parse.
+#       topicList - A reference to the list of <NaturalDocs::Parser::ParsedTopics> being built by the file.
+#
+#   Returns:
+#
+#       The array ( autoTopics, scopeRecord ).
+#
+#       autoTopics - An arrayref of automatically generated topics from the file, or undef if none.
+#       scopeRecord - An arrayref of <NaturalDocs::Languages::Advanced::ScopeChanges>, or undef if none.
+#
+sub ParseFile #(sourceFile, topicsList)
+    {
+    my ($self, $sourceFile, $topicsList) = @_;
+
+    $self->ParseForCommentsAndTokens($sourceFile, [ '//' ], [ '/*', '*/' ], [ '///' ], [ '/**', '*/' ] );
+
+    my $tokens = $self->Tokens();
+    my $index = 0;
+    my $lineNumber = 1;
+
+    while ($index < scalar @$tokens)
+        {
+        if ($self->TryToSkipWhitespace(\$index, \$lineNumber) ||
+            $self->TryToGetNamespace(\$index, \$lineNumber) ||
+            $self->TryToGetUsing(\$index, \$lineNumber) ||
+            $self->TryToGetClass(\$index, \$lineNumber) ||
+            $self->TryToGetFunction(\$index, \$lineNumber) ||
+            $self->TryToGetOverloadedOperator(\$index, \$lineNumber) ||
+            $self->TryToGetVariable(\$index, \$lineNumber) ||
+            $self->TryToGetEnum(\$index, \$lineNumber) )
+            {
+            # The functions above will handle everything.
+            }
+
+        elsif ($tokens->[$index] eq '{')
+            {
+            $self->StartScope('}', $lineNumber, undef, undef, undef);
+            $index++;
+            }
+
+        elsif ($tokens->[$index] eq '}')
+            {
+            if ($self->ClosingScopeSymbol() eq '}')
+                {  $self->EndScope($lineNumber);  };
+
+            $index++;
+            }
+
+        else
+            {
+            $self->SkipRestOfStatement(\$index, \$lineNumber);
+            };
+        };
+
+
+    # Don't need to keep these around.
+    $self->ClearTokens();
+
+
+    my $autoTopics = $self->AutoTopics();
+
+    my $scopeRecord = $self->ScopeRecord();
+    if (defined $scopeRecord && !scalar @$scopeRecord)
+        {  $scopeRecord = undef;  };
+
+    return ( $autoTopics, $scopeRecord );
+    };
+
+
+
+###############################################################################
+# Group: Statement Parsing Functions
+# All functions here assume that the current position is at the beginning of a statement.
+#
+# Note for developers: I am well aware that the code in these functions do not check if we're past the end of the tokens as
+# often as it should.  We're making use of the fact that Perl will always return undef in these cases to keep the code simpler.
+
+
+#
+#   Function: TryToGetNamespace
+#
+#   Determines whether the position is at a namespace declaration statement, and if so, adjusts the scope, skips it, and returns
+#   true.
+#
+#   Why no topic?:
+#
+#       The main reason we don't create a Natural Docs topic for a namespace is because in order to declare class A.B.C in C#,
+#       you must do this:
+#
+#       > namespace A.B
+#       >    {
+#       >    class C
+#       >        { ... }
+#       >    }
+#
+#       That would result in a namespace topic whose only purpose is really to qualify C.  It would take the default page title, and
+#       thus the default menu title.  So if you have files for A.B.X, A.B.Y, and A.B.Z, they all will appear as A.B on the menu.
+#
+#       If something actually appears in the namespace besides a class, it will be handled by
+#       <NaturalDocs::Parser->AddPackageDelineators()>.  That function will add a package topic to correct the scope.
+#
+#       If the user actually documented it, it will still appear because of the manual topic.
+#
+sub TryToGetNamespace #(indexRef, lineNumberRef)
+    {
+    my ($self, $indexRef, $lineNumberRef) = @_;
+    my $tokens = $self->Tokens();
+
+    if (lc($tokens->[$$indexRef]) ne 'namespace')
+        {  return undef;  };
+
+    my $index = $$indexRef + 1;
+    my $lineNumber = $$lineNumberRef;
+
+    if (!$self->TryToSkipWhitespace(\$index, \$lineNumber))
+        {  return undef;  };
+
+    my $name;
+
+    while ($tokens->[$index] =~ /^[a-z_\.\@]/i)
+        {
+        $name .= $tokens->[$index];
+        $index++;
+        };
+
+    if (!defined $name)
+        {  return undef;  };
+
+    $self->TryToSkipWhitespace(\$index, \$lineNumber);
+
+    if ($tokens->[$index] ne '{')
+        {  return undef;  };
+
+    $index++;
+
+
+    # We found a valid one if we made it this far.
+
+    my $autoTopic = NaturalDocs::Parser::ParsedTopic->New(::TOPIC_CLASS(), $name,
+                                                                                         $self->CurrentScope(), $self->CurrentUsing(),
+                                                                                         undef,
+                                                                                         undef, undef, $$lineNumberRef);
+
+    # We don't add an auto-topic for namespaces.  See the function documentation above.
+
+    NaturalDocs::Parser->OnClass($autoTopic->Package());
+
+    $self->StartScope('}', $lineNumber, $autoTopic->Package());
+
+    $$indexRef = $index;
+    $$lineNumberRef = $lineNumber;
+
+    return 1;
+    };
+
+
+#
+#   Function: TryToGetClass
+#
+#   Determines whether the position is at a class declaration statement, and if so, generates a topic for it, skips it, and
+#   returns true.
+#
+#   Supported Syntaxes:
+#
+#       - Classes
+#       - Structs
+#       - Interfaces
+#
+sub TryToGetClass #(indexRef, lineNumberRef)
+    {
+    my ($self, $indexRef, $lineNumberRef) = @_;
+    my $tokens = $self->Tokens();
+
+    my $index = $$indexRef;
+    my $lineNumber = $$lineNumberRef;
+
+    my $startIndex = $index;
+    my $startLine = $lineNumber;
+    my $needsPrototype = 0;
+
+    if ($self->TryToSkipAttributes(\$index, \$lineNumber))
+        {  $self->TryToSkipWhitespace(\$index, \$lineNumber);  }
+
+    my @modifiers;
+
+    while ($tokens->[$index] =~ /^[a-z]/i &&
+              !exists $classKeywords{lc($tokens->[$index])} &&
+              exists $classModifiers{lc($tokens->[$index])} )
+        {
+        push @modifiers, lc($tokens->[$index]);
+        $index++;
+
+        $self->TryToSkipWhitespace(\$index, \$lineNumber);
+        };
+
+    if (!exists $classKeywords{lc($tokens->[$index])})
+        {  return undef;  };
+
+    my $lcClassKeyword = lc($tokens->[$index]);
+
+    $index++;
+
+    if (!$self->TryToSkipWhitespace(\$index, \$lineNumber))
+        {  return undef;  };
+
+    my $name;
+
+    while ($tokens->[$index] =~ /^[a-z_\@]/i)
+        {
+        $name .= $tokens->[$index];
+        $index++;
+        };
+
+    if (!defined $name)
+        {  return undef;  };
+
+    $self->TryToSkipWhitespace(\$index, \$lineNumber);
+
+    if ($tokens->[$index] eq '<')
+    	{
+    	# XXX: This is half-assed.
+    	$index++;
+    	$needsPrototype = 1;
+
+    	while ($index < scalar @$tokens && $tokens->[$index] ne '>')
+    		{
+    		$index++;
+    		}
+
+    	if ($index < scalar @$tokens)
+    		{
+    		$index++;
+    		}
+    	else
+    		{  return undef;  }
+
+        $self->TryToSkipWhitespace(\$index, \$lineNumber);
+    	}
+
+    my @parents;
+
+    if ($tokens->[$index] eq ':')
+        {
+        do
+            {
+            $index++;
+
+            $self->TryToSkipWhitespace(\$index, \$lineNumber);
+
+            my $parentName;
+
+            while ($tokens->[$index] =~ /^[a-z_\.\@]/i)
+                {
+                $parentName .= $tokens->[$index];
+                $index++;
+                };
+
+	        if ($tokens->[$index] eq '<')
+	            {
+	            # XXX: This is still half-assed.
+	            $index++;
+	            $needsPrototype = 1;
+
+	            while ($index < scalar @$tokens && $tokens->[$index] ne '>')
+	                {
+	                $index++;
+	                }
+
+	            if ($index < scalar @$tokens)
+	                {
+	                $index++;
+	                }
+	            else
+	                {  return undef;  }
+
+	            $self->TryToSkipWhitespace(\$index, \$lineNumber);
+	            }
+
+            if (!defined $parentName)
+                {  return undef;  };
+
+            push @parents, NaturalDocs::SymbolString->FromText($parentName);
+
+            $self->TryToSkipWhitespace(\$index, \$lineNumber);
+            }
+        while ($tokens->[$index] eq ',')
+        };
+
+    if (lc($tokens->[$index]) eq 'where')
+    	{
+    	# XXX: This is also half-assed
+    	$index++;
+
+    	while ($index < scalar @$tokens && $tokens->[$index] ne '{')
+    		{
+    		$index++;
+    		}
+    	}
+
+    if ($tokens->[$index] ne '{')
+        {  return undef;  };
+
+
+    # If we made it this far, we have a valid class declaration.
+
+    my @scopeIdentifiers = NaturalDocs::SymbolString->IdentifiersOf($self->CurrentScope());
+    $name = join('.', @scopeIdentifiers, $name);
+
+    my $topicType;
+
+    if ($lcClassKeyword eq 'interface')
+        {  $topicType = ::TOPIC_INTERFACE();  }
+    else
+        {  $topicType = ::TOPIC_CLASS();  };
+
+    my $prototype;
+
+    if ($needsPrototype)
+    	{
+    	$prototype = $self->CreateString($startIndex, $index);
+    	}
+
+    my $autoTopic = NaturalDocs::Parser::ParsedTopic->New($topicType, $name,
+                                                                                         undef, $self->CurrentUsing(),
+                                                                                         $prototype,
+                                                                                         undef, undef, $$lineNumberRef);
+
+    $self->AddAutoTopic($autoTopic);
+    NaturalDocs::Parser->OnClass($autoTopic->Package());
+
+    foreach my $parent (@parents)
+        {
+        NaturalDocs::Parser->OnClassParent($autoTopic->Package(), $parent, $self->CurrentScope(), undef,
+                                                               ::RESOLVE_RELATIVE());
+        };
+
+    $self->StartScope('}', $lineNumber, $autoTopic->Package());
+
+    $index++;
+
+    $$indexRef = $index;
+    $$lineNumberRef = $lineNumber;
+
+    return 1;
+    };
+
+
+#
+#   Function: TryToGetUsing
+#
+#   Determines whether the position is at a using statement, and if so, adds it to the current scope, skips it, and returns
+#	true.
+#
+#	Supported:
+#
+#       - Using
+#
+#	Unsupported:
+#
+#		- Using with alias
+#
+sub TryToGetUsing #(indexRef, lineNumberRef)
+    {
+    my ($self, $indexRef, $lineNumberRef) = @_;
+    my $tokens = $self->Tokens();
+
+    my $index = $$indexRef;
+    my $lineNumber = $$lineNumberRef;
+
+    if (lc($tokens->[$index]) ne 'using')
+        {  return undef;  };
+
+    $index++;
+    $self->TryToSkipWhitespace(\$index, \$lineNumber);
+
+    my $name;
+
+    while ($tokens->[$index] =~ /^[a-z_\@\.]/i)
+        {
+        $name .= $tokens->[$index];
+        $index++;
+        };
+
+    if ($tokens->[$index] ne ';' ||
+		!defined $name)
+        {  return undef;  };
+
+    $index++;
+
+
+    $self->AddUsing( NaturalDocs::SymbolString->FromText($name) );
+
+    $$indexRef = $index;
+    $$lineNumberRef = $lineNumber;
+
+    return 1;
+    };
+
+
+
+#
+#   Function: TryToGetFunction
+#
+#   Determines if the position is on a function declaration, and if so, generates a topic for it, skips it, and returns true.
+#
+#   Supported Syntaxes:
+#
+#       - Functions
+#       - Constructors
+#       - Destructors
+#       - Properties
+#       - Indexers
+#       - Delegates
+#       - Events
+#
+sub TryToGetFunction #(indexRef, lineNumberRef)
+    {
+    my ($self, $indexRef, $lineNumberRef) = @_;
+    my $tokens = $self->Tokens();
+
+    my $index = $$indexRef;
+    my $lineNumber = $$lineNumberRef;
+
+    if ($self->TryToSkipAttributes(\$index, \$lineNumber))
+        {  $self->TryToSkipWhitespace(\$index, \$lineNumber);  };
+
+    my $startIndex = $index;
+    my $startLine = $lineNumber;
+
+    my @modifiers;
+
+    while ($tokens->[$index] =~ /^[a-z]/i &&
+              exists $functionModifiers{lc($tokens->[$index])} )
+        {
+        push @modifiers, lc($tokens->[$index]);
+        $index++;
+
+        $self->TryToSkipWhitespace(\$index, \$lineNumber);
+        };
+
+    my $isDelegate;
+    my $isEvent;
+
+    if (lc($tokens->[$index]) eq 'delegate')
+        {
+        $isDelegate = 1;
+        $index++;
+        $self->TryToSkipWhitespace(\$index, \$lineNumber);
+        }
+    elsif (lc($tokens->[$index]) eq 'event')
+        {
+        $isEvent = 1;
+        $index++;
+        $self->TryToSkipWhitespace(\$index, \$lineNumber);
+        };
+
+    my $returnType = $self->TryToGetType(\$index, \$lineNumber);
+
+    $self->TryToSkipWhitespace(\$index, \$lineNumber);
+
+    my $name;
+    my $lastNameWord;
+
+    while ($tokens->[$index] =~ /^[a-z\_\@\.\~\<]/i)
+        {
+        $name .= $tokens->[$index];
+
+        # Ugly hack, but what else is new?  For explicit generic interface definitions, such as:
+        # IDObjectType System.Collections.Generic.IEnumerator<IDObjectType>.Current
+
+        if ($tokens->[$index] eq '<')
+        	{
+        	do
+        		{
+	        	$index++;
+        		$name .= $tokens->[$index];
+        		}
+        	while ($index < @$tokens && $tokens->[$index] ne '>');
+        	}
+
+        $lastNameWord = $tokens->[$index];
+        $index++;
+        };
+
+    if (!defined $name)
+        {
+        # Constructors and destructors don't have return types.  It's possible their names were mistaken for the return type.
+        if (defined $returnType)
+            {
+            $name = $returnType;
+            $returnType = undef;
+
+            $name =~ /([a-z0-9_]+)$/i;
+            $lastNameWord = $1;
+            }
+        else
+            {  return undef;  };
+        };
+
+    # If there's no return type, make sure it's a constructor or destructor.
+    if (!defined $returnType)
+        {
+        my @identifiers = NaturalDocs::SymbolString->IdentifiersOf( $self->CurrentScope() );
+
+        if ($lastNameWord ne $identifiers[-1])
+            {  return undef;  };
+        };
+
+    $self->TryToSkipWhitespace(\$index, \$lineNumber);
+
+
+    # Skip the brackets on indexers.
+    if ($tokens->[$index] eq '[' && lc($lastNameWord) eq 'this')
+        {
+        # This should jump the brackets completely.
+        $self->GenericSkip(\$index, \$lineNumber);
+        $self->TryToSkipWhitespace(\$index, \$lineNumber);
+
+        $name .= '[]';
+        };
+
+
+    # Properties, indexers, events with braces
+
+    if ($tokens->[$index] eq '{')
+        {
+        my $prototype = $self->CreateString($startIndex, $index);
+
+        $index++;
+        $self->TryToSkipWhitespace(\$index, \$lineNumber);
+
+        my ($aWord, $bWord, $hasA, $hasB);
+
+        if ($isEvent)
+            {
+            $aWord = 'add';
+            $bWord = 'remove';
+            }
+        else
+            {
+            $aWord = 'get';
+            $bWord = 'set';
+            };
+
+        while ($index < scalar @$tokens)
+            {
+            if ($self->TryToSkipAttributes(\$index, \$lineNumber))
+                {  $self->TryToSkipWhitespace(\$index, \$lineNumber);  };
+
+            if (lc($tokens->[$index]) eq $aWord)
+                {  $hasA = 1;  }
+            elsif (lc($tokens->[$index]) eq $bWord)
+                {  $hasB = 1;  }
+            elsif ($tokens->[$index] eq '}')
+                {
+                $index++;
+                last;
+                };
+
+            $self->SkipRestOfStatement(\$index, \$lineNumber);
+            $self->TryToSkipWhitespace(\$index, \$lineNumber);
+            };
+
+        if ($hasA && $hasB)
+            {  $prototype .= ' { ' . $aWord . ', ' . $bWord . ' }';  }
+        elsif ($hasA)
+            {  $prototype .= ' { ' . $aWord . ' }';  }
+        elsif ($hasB)
+            {  $prototype .= ' { ' . $bWord . ' }';  };
+
+        $prototype = $self->NormalizePrototype($prototype);
+
+        my $topicType = ( $isEvent ? ::TOPIC_EVENT() : ::TOPIC_PROPERTY() );
+
+        $self->AddAutoTopic(NaturalDocs::Parser::ParsedTopic->New($topicType, $name,
+                                                                                                  $self->CurrentScope(), $self->CurrentUsing(),
+                                                                                                  $prototype,
+                                                                                                  undef, undef, $startLine));
+        }
+
+
+    # Functions, constructors, destructors, delegates.
+
+    elsif ($tokens->[$index] eq '(')
+        {
+        # This should jump the parenthesis completely.
+        $self->GenericSkip(\$index, \$lineNumber);
+
+        my $topicType = ( $isDelegate ? ::TOPIC_DELEGATE() : ::TOPIC_FUNCTION() );
+        my $prototype = $self->NormalizePrototype( $self->CreateString($startIndex, $index) );
+
+        $self->AddAutoTopic(NaturalDocs::Parser::ParsedTopic->New($topicType, $name,
+                                                                                                  $self->CurrentScope(), $self->CurrentUsing(),
+                                                                                                  $prototype,
+                                                                                                  undef, undef, $startLine));
+
+        $self->SkipRestOfStatement(\$index, \$lineNumber);
+        }
+
+
+    # Events without braces
+
+    elsif ($isEvent && $tokens->[$index] eq ';')
+        {
+        my $prototype = $self->NormalizePrototype( $self->CreateString($startIndex, $index) );
+
+        $self->AddAutoTopic(NaturalDocs::Parser::ParsedTopic->New(::TOPIC_EVENT(), $name,
+                                                                                                  $self->CurrentScope(), $self->CurrentUsing(),
+                                                                                                  $prototype,
+                                                                                                  undef, undef, $startLine));
+        $index++;
+        }
+
+    else
+        {  return undef;  };
+
+
+    # We succeeded if we got this far.
+
+    $$indexRef = $index;
+    $$lineNumberRef = $lineNumber;
+
+    return 1;
+    };
+
+
+#
+#   Function: TryToGetOverloadedOperator
+#
+#   Determines if the position is on an operator overload declaration, and if so, generates a topic for it, skips it, and returns true.
+#
+sub TryToGetOverloadedOperator #(indexRef, lineNumberRef)
+    {
+    my ($self, $indexRef, $lineNumberRef) = @_;
+    my $tokens = $self->Tokens();
+
+    my $index = $$indexRef;
+    my $lineNumber = $$lineNumberRef;
+
+    if ($self->TryToSkipAttributes(\$index, \$lineNumber))
+        {  $self->TryToSkipWhitespace(\$index, \$lineNumber);  };
+
+    my $startIndex = $index;
+    my $startLine = $lineNumber;
+
+    my @modifiers;
+
+    while ($tokens->[$index] =~ /^[a-z]/i &&
+              exists $functionModifiers{lc($tokens->[$index])} )
+        {
+        push @modifiers, lc($tokens->[$index]);
+        $index++;
+
+        $self->TryToSkipWhitespace(\$index, \$lineNumber);
+        };
+
+
+    my $name;
+
+
+    # Casting operators.
+
+    if (lc($tokens->[$index]) eq 'implicit' || lc($tokens->[$index]) eq 'explicit')
+        {
+        $index++;
+
+        $self->TryToSkipWhitespace(\$index, \$lineNumber);
+
+        if (lc($tokens->[$index]) ne 'operator')
+            {  return undef;  };
+
+        $index++;
+        $self->TryToSkipWhitespace(\$index, \$lineNumber);
+
+        $name = $self->TryToGetType(\$index, \$lineNumber);
+
+        if (!defined $name)
+            {  return undef;  };
+        }
+
+
+    # Symbol operators.
+
+    else
+        {
+        if (!$self->TryToGetType(\$index, \$lineNumber))
+            {  return undef;  };
+
+        $self->TryToSkipWhitespace(\$index, \$lineNumber);
+
+        if (lc($tokens->[$index]) ne 'operator')
+            {  return undef;  };
+
+        $index++;
+
+        $self->TryToSkipWhitespace(\$index, \$lineNumber);
+
+        if (lc($tokens->[$index]) eq 'true' || lc($tokens->[$index]) eq 'false')
+            {
+            $name = $tokens->[$index];
+            $index++;
+            }
+        else
+            {
+            while ($tokens->[$index] =~ /^[\+\-\!\~\*\/\%\&\|\^\<\>\=]$/)
+                {
+                $name .= $tokens->[$index];
+                $index++;
+                };
+            };
+        };
+
+    $self->TryToSkipWhitespace(\$index, \$lineNumber);
+
+    if ($tokens->[$index] ne '(')
+        {  return undef;  };
+
+    # This should skip the parenthesis completely.
+    $self->GenericSkip(\$index, \$lineNumber);
+
+    my $prototype = $self->NormalizePrototype( $self->CreateString($startIndex, $index) );
+
+    $self->AddAutoTopic(NaturalDocs::Parser::ParsedTopic->New(::TOPIC_FUNCTION(), 'operator ' . $name,
+                                                                                              $self->CurrentScope(), $self->CurrentUsing(),
+                                                                                              $prototype,
+                                                                                              undef, undef, $startLine));
+
+    $self->SkipRestOfStatement(\$index, \$lineNumber);
+
+
+    # We succeeded if we got this far.
+
+    $$indexRef = $index;
+    $$lineNumberRef = $lineNumber;
+
+    return 1;
+    };
+
+
+#
+#   Function: TryToGetVariable
+#
+#   Determines if the position is on a variable declaration statement, and if so, generates a topic for each variable, skips the
+#   statement, and returns true.
+#
+#   Supported Syntaxes:
+#
+#       - Variables
+#       - Constants
+#
+sub TryToGetVariable #(indexRef, lineNumberRef)
+    {
+    my ($self, $indexRef, $lineNumberRef) = @_;
+    my $tokens = $self->Tokens();
+
+    my $index = $$indexRef;
+    my $lineNumber = $$lineNumberRef;
+
+    if ($self->TryToSkipAttributes(\$index, \$lineNumber))
+        {  $self->TryToSkipWhitespace(\$index, \$lineNumber);  };
+
+    my $startIndex = $index;
+    my $startLine = $lineNumber;
+
+    my @modifiers;
+
+    while ($tokens->[$index] =~ /^[a-z]/i &&
+              exists $variableModifiers{lc($tokens->[$index])} )
+        {
+        push @modifiers, lc($tokens->[$index]);
+        $index++;
+
+        $self->TryToSkipWhitespace(\$index, \$lineNumber);
+        };
+
+    my $type;
+    if (lc($tokens->[$index]) eq 'const')
+        {
+        $type = ::TOPIC_CONSTANT();
+        $index++;
+        $self->TryToSkipWhitespace(\$index, \$lineNumber);
+        }
+    else
+        {
+        $type = ::TOPIC_VARIABLE();
+        };
+
+    if (!$self->TryToGetType(\$index, \$lineNumber))
+        {  return undef;  };
+
+    my $endTypeIndex = $index;
+
+    $self->TryToSkipWhitespace(\$index, \$lineNumber);
+
+    my @names;
+
+    for (;;)
+        {
+        my $name;
+
+        while ($tokens->[$index] =~ /^[a-z\@\_]/i)
+            {
+            $name .= $tokens->[$index];
+            $index++;
+            };
+
+        $self->TryToSkipWhitespace(\$index, \$lineNumber);
+
+        if ($tokens->[$index] eq '=')
+            {
+            do
+                {
+                $self->GenericSkip(\$index, \$lineNumber);
+                }
+            while ($tokens->[$index] ne ',' && $tokens->[$index] ne ';');
+            };
+
+        push @names, $name;
+
+        if ($tokens->[$index] eq ';')
+            {
+            $index++;
+            last;
+            }
+        elsif ($tokens->[$index] eq ',')
+            {
+            $index++;
+            $self->TryToSkipWhitespace(\$index, \$lineNumber);
+            }
+        else
+            {  return undef;  };
+        };
+
+
+    # We succeeded if we got this far.
+
+    my $prototypePrefix = $self->CreateString($startIndex, $endTypeIndex);
+
+    foreach my $name (@names)
+        {
+        my $prototype = $self->NormalizePrototype( $prototypePrefix . ' ' . $name );
+
+        $self->AddAutoTopic(NaturalDocs::Parser::ParsedTopic->New($type, $name,
+                                                                                                  $self->CurrentScope(), $self->CurrentUsing(),
+                                                                                                  $prototype,
+                                                                                                  undef, undef, $startLine));
+        };
+
+    $$indexRef = $index;
+    $$lineNumberRef = $lineNumber;
+
+    return 1;
+    };
+
+
+#
+#   Function: TryToGetEnum
+#
+#   Determines if the position is on an enum declaration statement, and if so, generates a topic for it.
+#
+#   Supported Syntaxes:
+#
+#       - Enums
+#       - Enums with declared types
+#
+#   Unsupported:
+#
+#       - Documenting the members automatically
+#
+sub TryToGetEnum #(indexRef, lineNumberRef)
+    {
+    my ($self, $indexRef, $lineNumberRef) = @_;
+    my $tokens = $self->Tokens();
+
+    my $index = $$indexRef;
+    my $lineNumber = $$lineNumberRef;
+
+    if ($self->TryToSkipAttributes(\$index, \$lineNumber))
+        {  $self->TryToSkipWhitespace(\$index, \$lineNumber);  };
+
+    my $startIndex = $index;
+    my $startLine = $lineNumber;
+
+    my @modifiers;
+
+    while ($tokens->[$index] =~ /^[a-z]/i &&
+              exists $variableModifiers{lc($tokens->[$index])} )
+        {
+        push @modifiers, lc($tokens->[$index]);
+        $index++;
+
+        $self->TryToSkipWhitespace(\$index, \$lineNumber);
+        };
+
+    if (lc($tokens->[$index]) ne 'enum')
+        {  return undef;  }
+
+    $index++;
+    $self->TryToSkipWhitespace(\$index, \$lineNumber);
+
+    my $name;
+
+    while ($tokens->[$index] =~ /^[a-z\@\_]/i)
+        {
+        $name .= $tokens->[$index];
+        $index++;
+        };
+
+    $self->TryToSkipWhitespace(\$index, \$lineNumber);
+
+    if ($tokens->[$index] eq ':')
+        {
+        $index++;
+        $self->TryToSkipWhitespace(\$index, \$lineNumber);
+
+        if (!exists $enumTypes{ lc($tokens->[$index]) })
+            {  return undef;  }
+
+        $index++;
+        $self->TryToSkipWhitespace(\$index, \$lineNumber);
+        }
+
+    if ($tokens->[$index] ne '{')
+        {  return undef;  }
+
+    # We succeeded if we got this far.
+
+    my $prototype = $self->CreateString($startIndex, $index);
+    $prototype = $self->NormalizePrototype( $prototype );
+
+    $self->SkipRestOfStatement(\$index, \$lineNumber);
+
+    $self->AddAutoTopic(NaturalDocs::Parser::ParsedTopic->New(::TOPIC_ENUMERATION(), $name,
+                                                                                              $self->CurrentScope(), $self->CurrentUsing(),
+                                                                                              $prototype,
+                                                                                              undef, undef, $startLine));
+
+    $$indexRef = $index;
+    $$lineNumberRef = $lineNumber;
+
+    return 1;
+    };
+
+
+#
+#   Function: TryToGetType
+#
+#   Determines if the position is on a type identifier, and if so, skips it and returns it as a string.  This function does _not_ allow
+#   modifiers.  You must take care of those beforehand.
+#
+sub TryToGetType #(indexRef, lineNumberRef)
+    {
+    my ($self, $indexRef, $lineNumberRef) = @_;
+    my $tokens = $self->Tokens();
+
+    my $name;
+    my $index = $$indexRef;
+    my $lineNumber = $$lineNumberRef;
+
+    while ($tokens->[$index] =~ /^[a-z\@\.\_]/i)
+        {
+        if (exists $impossibleTypeWords{ lc($tokens->[$index]) } && $name !~ /\@$/)
+            {  return undef;  };
+
+        $name .= $tokens->[$index];
+        $index++;
+        };
+
+    if (!defined $name)
+        {  return undef;  };
+
+	$self->TryToSkipWhitespace(\$index, \$lineNumber);
+
+	if ($tokens->[$index] eq '?')
+		{
+		$name .= '?';
+		$index++;
+
+		$self->TryToSkipWhitespace(\$index, \$lineNumber);
+		}
+
+    if ($tokens->[$index] eq '<')
+    	{
+    	# XXX: This is half-assed.
+    	$name .= '<';
+    	$index++;
+
+    	while ($index < scalar @$tokens && $tokens->[$index] ne '>')
+    		{
+    		$name .= $tokens->[$index];
+    		$index++;
+    		}
+
+    	if ($index < scalar @$tokens)
+    		{
+    		$name .= '>';
+    		$index++;
+    		}
+    	else
+    		{  return undef;  }
+
+		$self->TryToSkipWhitespace(\$index, \$lineNumber);
+    	}
+
+    while ($tokens->[$index] eq '[')
+        {
+        $name .= '[';
+        $index++;
+
+        while ($tokens->[$index] eq ',')
+            {
+            $name .= ',';
+            $index++;
+            };
+
+        if ($tokens->[$index] eq ']')
+            {
+            $name .= ']';
+            $index++;
+            }
+        else
+            {  return undef;  }
+        };
+
+    $$indexRef = $index;
+    $$lineNumberRef = $lineNumber;
+
+    return $name;
+    };
+
+
+
+###############################################################################
+# Group: Low Level Parsing Functions
+
+
+#
+#   Function: GenericSkip
+#
+#   Advances the position one place through general code.
+#
+#   - If the position is on a string, it will skip it completely.
+#   - If the position is on an opening symbol, it will skip until the past the closing symbol.
+#   - If the position is on whitespace (including comments and preprocessing directives), it will skip it completely.
+#   - Otherwise it skips one token.
+#
+#   Parameters:
+#
+#       indexRef - A reference to the current index.
+#       lineNumberRef - A reference to the current line number.
+#
+sub GenericSkip #(indexRef, lineNumberRef)
+    {
+    my ($self, $indexRef, $lineNumberRef) = @_;
+    my $tokens = $self->Tokens();
+
+    # We can ignore the scope stack because we're just skipping everything without parsing, and we need recursion anyway.
+    if ($tokens->[$$indexRef] eq '{')
+        {
+        $$indexRef++;
+        $self->GenericSkipUntilAfter($indexRef, $lineNumberRef, '}');
+        }
+    elsif ($tokens->[$$indexRef] eq '(')
+        {
+        $$indexRef++;
+        $self->GenericSkipUntilAfter($indexRef, $lineNumberRef, ')');
+        }
+    elsif ($tokens->[$$indexRef] eq '[')
+        {
+        $$indexRef++;
+        $self->GenericSkipUntilAfter($indexRef, $lineNumberRef, ']');
+        }
+
+    elsif ($self->TryToSkipWhitespace($indexRef, $lineNumberRef) ||
+            $self->TryToSkipString($indexRef, $lineNumberRef))
+        {
+        }
+
+    else
+        {  $$indexRef++;  };
+    };
+
+
+#
+#   Function: GenericSkipUntilAfter
+#
+#   Advances the position via <GenericSkip()> until a specific token is reached and passed.
+#
+sub GenericSkipUntilAfter #(indexRef, lineNumberRef, token)
+    {
+    my ($self, $indexRef, $lineNumberRef, $token) = @_;
+    my $tokens = $self->Tokens();
+
+    while ($$indexRef < scalar @$tokens && $tokens->[$$indexRef] ne $token)
+        {  $self->GenericSkip($indexRef, $lineNumberRef);  };
+
+    if ($tokens->[$$indexRef] eq "\n")
+        {  $$lineNumberRef++;  };
+    $$indexRef++;
+    };
+
+
+#
+#   Function: SkipRestOfStatement
+#
+#   Advances the position via <GenericSkip()> until after the end of the current statement, which is defined as a semicolon or
+#   a brace group.  Of course, either of those appearing inside parenthesis, a nested brace group, etc. don't count.
+#
+sub SkipRestOfStatement #(indexRef, lineNumberRef)
+    {
+    my ($self, $indexRef, $lineNumberRef) = @_;
+    my $tokens = $self->Tokens();
+
+    while ($$indexRef < scalar @$tokens &&
+             $tokens->[$$indexRef] ne ';' &&
+             $tokens->[$$indexRef] ne '{')
+        {
+        $self->GenericSkip($indexRef, $lineNumberRef);
+        };
+
+    if ($tokens->[$$indexRef] eq ';')
+        {  $$indexRef++;  }
+    elsif ($tokens->[$$indexRef] eq '{')
+        {  $self->GenericSkip($indexRef, $lineNumberRef);  };
+    };
+
+
+#
+#   Function: TryToSkipString
+#   If the current position is on a string delimiter, skip past the string and return true.
+#
+#   Parameters:
+#
+#       indexRef - A reference to the index of the position to start at.
+#       lineNumberRef - A reference to the line number of the position.
+#
+#   Returns:
+#
+#       Whether the position was at a string.
+#
+#   Syntax Support:
+#
+#       - Supports quotes, apostrophes, and at-quotes.
+#
+sub TryToSkipString #(indexRef, lineNumberRef)
+    {
+    my ($self, $indexRef, $lineNumberRef) = @_;
+    my $tokens = $self->Tokens();
+
+    # The three string delimiters.  All three are Perl variables when preceded by a dollar sign.
+    if ($self->SUPER::TryToSkipString($indexRef, $lineNumberRef, '\'') ||
+        $self->SUPER::TryToSkipString($indexRef, $lineNumberRef, '"') )
+        {
+        return 1;
+        }
+    elsif ($tokens->[$$indexRef] eq '@' && $tokens->[$$indexRef+1] eq '"')
+        {
+        $$indexRef += 2;
+
+        # We need to do at-strings manually because backslash characters are accepted as regular characters, and two consecutive
+        # quotes are accepted as well.
+
+        while ($$indexRef < scalar @$tokens && !($tokens->[$$indexRef] eq '"' && $tokens->[$$indexRef+1] ne '"') )
+            {
+            if ($tokens->[$$indexRef] eq '"')
+                {
+                # This is safe because the while condition will only let through quote pairs.
+                $$indexRef += 2;
+                }
+            elsif ($tokens->[$$indexRef] eq "\n")
+                {
+                $$indexRef++;
+                $$lineNumberRef++;
+                }
+            else
+                {
+                $$indexRef++;
+                };
+            };
+
+        # Skip the closing quote.
+        if ($$indexRef < scalar @$tokens)
+            {  $$indexRef++;  };
+
+        return 1;
+        }
+    else
+        {  return undef;  };
+    };
+
+
+#
+#   Function: TryToSkipAttributes
+#   If the current position is on an attribute section, skip it and return true.  Skips multiple attribute sections if they appear
+#   consecutively.
+#
+sub TryToSkipAttributes #(indexRef, lineNumberRef)
+    {
+    my ($self, $indexRef, $lineNumberRef) = @_;
+    my $tokens = $self->Tokens();
+
+    my $success;
+
+    while ($tokens->[$$indexRef] eq '[')
+        {
+        $success = 1;
+        $$indexRef++;
+        $self->GenericSkipUntilAfter($indexRef, $lineNumberRef, ']');
+        $self->TryToSkipWhitespace($indexRef, $lineNumberRef);
+        };
+
+    return $success;
+    };
+
+
+#
+#   Function: TryToSkipWhitespace
+#   If the current position is on a whitespace token, a line break token, a comment, or a preprocessing directive, it skips them
+#   and returns true.  If there are a number of these in a row, it skips them all.
+#
+sub TryToSkipWhitespace #(indexRef, lineNumberRef)
+    {
+    my ($self, $indexRef, $lineNumberRef) = @_;
+    my $tokens = $self->Tokens();
+
+    my $result;
+
+    while ($$indexRef < scalar @$tokens)
+        {
+        if ($tokens->[$$indexRef] =~ /^[ \t]/)
+            {
+            $$indexRef++;
+            $result = 1;
+            }
+        elsif ($tokens->[$$indexRef] eq "\n")
+            {
+            $$indexRef++;
+            $$lineNumberRef++;
+            $result = 1;
+            }
+        elsif ($self->TryToSkipComment($indexRef, $lineNumberRef) ||
+                $self->TryToSkipPreprocessingDirective($indexRef, $lineNumberRef))
+            {
+            $result = 1;
+            }
+        else
+            {  last;  };
+        };
+
+    return $result;
+    };
+
+
+#
+#   Function: TryToSkipComment
+#   If the current position is on a comment, skip past it and return true.
+#
+sub TryToSkipComment #(indexRef, lineNumberRef)
+    {
+    my ($self, $indexRef, $lineNumberRef) = @_;
+
+    return ( $self->TryToSkipLineComment($indexRef, $lineNumberRef) ||
+                $self->TryToSkipMultilineComment($indexRef, $lineNumberRef) );
+    };
+
+
+#
+#   Function: TryToSkipLineComment
+#   If the current position is on a line comment symbol, skip past it and return true.
+#
+sub TryToSkipLineComment #(indexRef, lineNumberRef)
+    {
+    my ($self, $indexRef, $lineNumberRef) = @_;
+    my $tokens = $self->Tokens();
+
+    if ($tokens->[$$indexRef] eq '/' && $tokens->[$$indexRef+1] eq '/')
+        {
+        $self->SkipRestOfLine($indexRef, $lineNumberRef);
+        return 1;
+        }
+    else
+        {  return undef;  };
+    };
+
+
+#
+#   Function: TryToSkipMultilineComment
+#   If the current position is on an opening comment symbol, skip past it and return true.
+#
+sub TryToSkipMultilineComment #(indexRef, lineNumberRef)
+    {
+    my ($self, $indexRef, $lineNumberRef) = @_;
+    my $tokens = $self->Tokens();
+
+    if ($tokens->[$$indexRef] eq '/' && $tokens->[$$indexRef+1] eq '*')
+        {
+        $self->SkipUntilAfter($indexRef, $lineNumberRef, '*', '/');
+        return 1;
+        }
+    else
+        {  return undef;  };
+    };
+
+
+#
+#   Function: TryToSkipPreprocessingDirective
+#   If the current position is on a preprocessing directive, skip past it and return true.
+#
+sub TryToSkipPreprocessingDirective #(indexRef, lineNumberRef)
+    {
+    my ($self, $indexRef, $lineNumberRef) = @_;
+    my $tokens = $self->Tokens();
+
+    if ($tokens->[$$indexRef] eq '#' && $self->IsFirstLineToken($$indexRef))
+        {
+        $self->SkipRestOfLine($indexRef, $lineNumberRef);
+        return 1;
+        }
+    else
+        {  return undef;  };
+    };
+
+
+1;

Added: trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Languages/PLSQL.pm
===================================================================
--- trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Languages/PLSQL.pm	                        (rev 0)
+++ trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Languages/PLSQL.pm	2009-07-01 15:30:00 UTC (rev 4275)
@@ -0,0 +1,319 @@
+###############################################################################
+#
+#   Class: NaturalDocs::Languages::PLSQL
+#
+###############################################################################
+#
+#   A subclass to handle the language variations of PL/SQL.
+#
+###############################################################################
+
+# 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::Languages::PLSQL;
+
+use base 'NaturalDocs::Languages::Simple';
+
+
+#
+#   Function: OnPrototypeEnd
+#
+#   Microsoft's SQL specifies parameters as shown below.
+#
+#   > CREATE PROCEDURE Test @as int, @foo int AS ...
+#
+#   Having a parameter @is or @as is perfectly valid even though those words are also used to end the prototype.  We need to
+#   ignore text-based enders preceded by an at sign.  Also note that it does not have parenthesis for parameter lists.  We need to
+#   skip all commas if the prototype doesn't have parenthesis but does have @ characters.
+#
+#	Identifiers such as function names may contain the characters $, #, and _, so if "as" or "is" appears directly after one of them
+#	we need to ignore the ender there as well.
+#
+#	> FUNCTION Something_is_something ...
+#
+#   Parameters:
+#
+#       type - The <TopicType> of the prototype.
+#       prototypeRef - A reference to the prototype so far, minus the ender in dispute.
+#       ender - The ender symbol.
+#
+#   Returns:
+#
+#       ENDER_ACCEPT - The ender is accepted and the prototype is finished.
+#       ENDER_IGNORE - The ender is rejected and parsing should continue.  Note that the prototype will be rejected as a whole
+#                                  if all enders are ignored before reaching the end of the code.
+#       ENDER_ACCEPT_AND_CONTINUE - The ender is accepted so the prototype may stand as is.  However, the prototype might
+#                                                          also continue on so continue parsing.  If there is no accepted ender between here and
+#                                                          the end of the code this version will be accepted instead.
+#       ENDER_REVERT_TO_ACCEPTED - The expedition from ENDER_ACCEPT_AND_CONTINUE failed.  Use the last accepted
+#                                                        version and end parsing.
+#
+sub OnPrototypeEnd #(type, prototypeRef, ender)
+    {
+    my ($self, $type, $prototypeRef, $ender) = @_;
+
+    # _ should be handled already.
+    if ($ender =~ /^[a-z]+$/i && substr($$prototypeRef, -1) =~ /^[\@\$\#]$/)
+        {  return ::ENDER_IGNORE();  }
+
+    elsif ($type eq ::TOPIC_FUNCTION() && $ender eq ',')
+        {
+        if ($$prototypeRef =~ /^[^\(]*\@/)
+            {  return ::ENDER_IGNORE();  }
+        else
+            {  return ::ENDER_ACCEPT();  };
+        }
+
+    else
+        {  return ::ENDER_ACCEPT();  };
+    };
+
+
+#
+#   Function: ParsePrototype
+#
+#   Overridden to handle Microsoft's parenthesisless version.  Otherwise just throws to the parent.
+#
+#   Parameters:
+#
+#       type - The <TopicType>.
+#       prototype - The text prototype.
+#
+#   Returns:
+#
+#       A <NaturalDocs::Languages::Prototype> object.
+#
+sub ParsePrototype #(type, prototype)
+    {
+    my ($self, $type, $prototype) = @_;
+
+    my $noParenthesisParameters = ($type eq ::TOPIC_FUNCTION() && $prototype =~ /^[^\(]*\@/);
+
+    if ($prototype !~ /\(.*[^ ].*\)/ && !$noParenthesisParameters)
+        {  return $self->SUPER::ParsePrototype($type, $prototype);  };
+
+
+
+    my ($beforeParameters, $afterParameters, $isAfterParameters);
+
+    if ($noParenthesisParameters)
+        {
+        ($beforeParameters, $prototype) = split(/\@/, $prototype, 2);
+        $prototype = '@' . $prototype;
+        };
+
+    my @tokens = $prototype =~ /([^\(\)\[\]\{\}\<\>\'\"\,]+|.)/g;
+
+    my $parameter;
+    my @parameterLines;
+
+    my @symbolStack;
+
+    foreach my $token (@tokens)
+        {
+        if ($isAfterParameters)
+            {  $afterParameters .= $token;  }
+
+        elsif ($symbolStack[-1] eq '\'' || $symbolStack[-1] eq '"')
+            {
+            if ($noParenthesisParameters || $symbolStack[0] eq '(')
+                {  $parameter .= $token;  }
+            else
+                {  $beforeParameters .= $token;  };
+
+            if ($token eq $symbolStack[-1])
+                {  pop @symbolStack;  };
+            }
+
+        elsif ($token =~ /^[\(\[\{\<\'\"]$/)
+            {
+            if ($noParenthesisParameters || $symbolStack[0] eq '(')
+                {  $parameter .= $token;  }
+            else
+                {  $beforeParameters .= $token;  };
+
+            push @symbolStack, $token;
+            }
+
+        elsif ( ($token eq ')' && $symbolStack[-1] eq '(') ||
+                 ($token eq ']' && $symbolStack[-1] eq '[') ||
+                 ($token eq '}' && $symbolStack[-1] eq '{') ||
+                 ($token eq '>' && $symbolStack[-1] eq '<') )
+            {
+            if (!$noParenthesisParameters && $token eq ')' && scalar @symbolStack == 1 && $symbolStack[0] eq '(')
+                {
+                $afterParameters .= $token;
+                $isAfterParameters = 1;
+                }
+            else
+                {  $parameter .= $token;  };
+
+            pop @symbolStack;
+            }
+
+        elsif ($token eq ',')
+            {
+            if (!scalar @symbolStack)
+                {
+                if ($noParenthesisParameters)
+                    {
+                    push @parameterLines, $parameter . $token;
+                    $parameter = undef;
+                    }
+                else
+                    {
+                    $beforeParameters .= $token;
+                    };
+                }
+            else
+                {
+                if (scalar @symbolStack == 1 && $symbolStack[0] eq '(' && !$noParenthesisParameters)
+                    {
+                    push @parameterLines, $parameter . $token;
+                    $parameter = undef;
+                    }
+                else
+                    {
+                    $parameter .= $token;
+                    };
+                };
+            }
+
+        else
+            {
+            if ($noParenthesisParameters || $symbolStack[0] eq '(')
+                {  $parameter .= $token;  }
+            else
+                {  $beforeParameters .= $token;  };
+            };
+        };
+
+    push @parameterLines, $parameter;
+
+    foreach my $item (\$beforeParameters, \$afterParameters)
+        {
+        $$item =~ s/^ //;
+        $$item =~ s/ $//;
+        }
+
+    my $prototypeObject = NaturalDocs::Languages::Prototype->New($beforeParameters, $afterParameters);
+
+
+    # Parse the actual parameters.
+
+    foreach my $parameterLine (@parameterLines)
+        {
+        $prototypeObject->AddParameter( $self->ParseParameterLine($parameterLine) );
+        };
+
+    return $prototypeObject;
+    };
+
+
+#
+#   Function: ParseParameterLine
+#
+#   Parses a prototype parameter line and returns it as a <NaturalDocs::Languages::Prototype::Parameter> object.
+#
+sub ParseParameterLine #(line)
+    {
+    my ($self, $line) = @_;
+
+    $line =~ s/^ //;
+    $line =~ s/ $//;
+
+    my @tokens = $line =~ /([^\(\)\[\]\{\}\<\>\'\"\:\=\ ]+|\:\=|.)/g;
+
+    my ($name, $type, $defaultValue, $defaultValuePrefix, $inType, $inDefaultValue);
+
+
+    my @symbolStack;
+
+    foreach my $token (@tokens)
+        {
+        if ($inDefaultValue)
+            {  $defaultValue .= $token;  }
+
+        elsif ($symbolStack[-1] eq '\'' || $symbolStack[-1] eq '"')
+            {
+            if ($inType)
+                {  $type .= $token;  }
+            else
+                {  $name .= $token;  };
+
+            if ($token eq $symbolStack[-1])
+                {  pop @symbolStack;  };
+            }
+
+        elsif ($token =~ /^[\(\[\{\<\'\"]$/)
+            {
+            if ($inType)
+                {  $type .= $token;  }
+            else
+                {  $name .= $token;  };
+
+            push @symbolStack, $token;
+            }
+
+        elsif ( ($token eq ')' && $symbolStack[-1] eq '(') ||
+                 ($token eq ']' && $symbolStack[-1] eq '[') ||
+                 ($token eq '}' && $symbolStack[-1] eq '{') ||
+                 ($token eq '>' && $symbolStack[-1] eq '<') )
+            {
+            if ($inType)
+                {  $type .= $token;  }
+            else
+                {  $name .= $token;  };
+
+            pop @symbolStack;
+            }
+
+        elsif ($token eq ' ')
+            {
+            if ($inType)
+                {  $type .= $token;  }
+            elsif (!scalar @symbolStack)
+                {  $inType = 1;  }
+            else
+                {  $name .= $token;  };
+            }
+
+        elsif ($token eq ':=' || $token eq '=')
+            {
+            if (!scalar @symbolStack)
+                {
+                $defaultValuePrefix = $token;
+                $inDefaultValue = 1;
+                }
+            elsif ($inType)
+                {  $type .= $token;  }
+            else
+                {  $name .= $token;  };
+            }
+
+        else
+            {
+            if ($inType)
+                {  $type .= $token;  }
+            else
+                {  $name .= $token;  };
+            };
+        };
+
+    foreach my $part (\$type, \$defaultValue)
+        {
+        $$part =~ s/ $//;
+        };
+
+    return NaturalDocs::Languages::Prototype::Parameter->New($type, undef, $name, undef, $defaultValue, $defaultValuePrefix);
+    };
+
+
+sub TypeBeforeParameter
+    {  return 0;  };
+
+1;

Added: trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Languages/Pascal.pm
===================================================================
--- trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Languages/Pascal.pm	                        (rev 0)
+++ trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Languages/Pascal.pm	2009-07-01 15:30:00 UTC (rev 4275)
@@ -0,0 +1,143 @@
+###############################################################################
+#
+#   Class: NaturalDocs::Languages::Pascal
+#
+###############################################################################
+#
+#   A subclass to handle the language variations of Pascal and Delphi.
+#
+###############################################################################
+
+# 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::Languages::Pascal;
+
+use base 'NaturalDocs::Languages::Simple';
+
+
+#
+#   hash: prototypeDirectives
+#
+#   An existence hash of all the directives that can appear after a function prototype and will be included.  The keys are the all
+#   lowercase keywords.
+#
+my %prototypeDirectives = ( 'overload' => 1,
+                                           'override' => 1,
+                                           'virtual' => 1,
+                                           'abstract' => 1,
+                                           'reintroduce' => 1,
+                                           'export' => 1,
+                                           'public' => 1,
+                                           'interrupt' => 1,
+                                           'register' => 1,
+                                           'pascal' => 1,
+                                           'cdecl' => 1,
+                                           'stdcall' => 1,
+                                           'popstack' => 1,
+                                           'saveregisters' => 1,
+                                           'inline' => 1,
+                                           'safecall' => 1 );
+
+#
+#   hash: longPrototypeDirectives
+#
+#   An existence hash of all the directives with parameters that can appear after a function prototype and will be included.  The
+#   keys are the all lowercase keywords.
+#
+my %longPrototypeDirectives = ( 'alias' => 1,
+                                                 'external' => 1 );
+
+#
+#   bool: checkingForDirectives
+#
+#   Set after the first function semicolon, which means we're in directives mode.
+#
+my $checkingForDirectives;
+
+
+#
+#   Function: OnCode
+#
+#   Just overridden to reset <checkingForDirectives>.
+#
+sub OnCode #(...)
+    {
+    my ($self, @parameters) = @_;
+
+    $checkingForDirectives = 0;
+
+    return $self->SUPER::OnCode(@parameters);
+    };
+
+
+#
+#   Function: OnPrototypeEnd
+#
+#   Pascal's syntax has directives after the prototype that should be included.
+#
+#   > function MyFunction ( param1: type ); virtual; abstract;
+#
+#   Parameters:
+#
+#       type - The <TopicType> of the prototype.
+#       prototypeRef - A reference to the prototype so far, minus the ender in dispute.
+#       ender - The ender symbol.
+#
+#   Returns:
+#
+#       ENDER_ACCEPT - The ender is accepted and the prototype is finished.
+#       ENDER_IGNORE - The ender is rejected and parsing should continue.  Note that the prototype will be rejected as a whole
+#                                  if all enders are ignored before reaching the end of the code.
+#       ENDER_ACCEPT_AND_CONTINUE - The ender is accepted so the prototype may stand as is.  However, the prototype might
+#                                                          also continue on so continue parsing.  If there is no accepted ender between here and
+#                                                          the end of the code this version will be accepted instead.
+#       ENDER_REVERT_TO_ACCEPTED - The expedition from ENDER_ACCEPT_AND_CONTINUE failed.  Use the last accepted
+#                                                        version and end parsing.
+#
+sub OnPrototypeEnd #(type, prototypeRef, ender)
+    {
+    my ($self, $type, $prototypeRef, $ender) = @_;
+
+    if ($type eq ::TOPIC_FUNCTION() && $ender eq ';')
+        {
+        if (!$checkingForDirectives)
+            {
+            $checkingForDirectives = 1;
+            return ::ENDER_ACCEPT_AND_CONTINUE();
+            }
+        elsif ($$prototypeRef =~ /;[ \t]*([a-z]+)([^;]*)$/i)
+            {
+            my ($lastDirective, $extra) = (lc($1), $2);
+
+            if (exists $prototypeDirectives{$lastDirective} && $extra =~ /^[ \t]*$/)
+                {  return ::ENDER_ACCEPT_AND_CONTINUE();  }
+            elsif (exists $longPrototypeDirectives{$lastDirective})
+                {  return ::ENDER_ACCEPT_AND_CONTINUE();  }
+            else
+                {  return ::ENDER_REVERT_TO_ACCEPTED();  };
+            }
+        else
+            {  return ::ENDER_REVERT_TO_ACCEPTED();  };
+        }
+    else
+        {  return ::ENDER_ACCEPT();  };
+    };
+
+
+sub ParseParameterLine #(...)
+    {
+    my ($self, @params) = @_;
+    return $self->SUPER::ParsePascalParameterLine(@params);
+    };
+
+sub TypeBeforeParameter
+    {
+    return 0;
+    };
+
+
+1;

Added: trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Languages/Perl.pm
===================================================================
--- trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Languages/Perl.pm	                        (rev 0)
+++ trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Languages/Perl.pm	2009-07-01 15:30:00 UTC (rev 4275)
@@ -0,0 +1,1370 @@
+###############################################################################
+#
+#   Class: NaturalDocs::Languages::Perl
+#
+###############################################################################
+#
+#   A subclass to handle the language variations of Perl.
+#
+#
+#   Topic: Language Support
+#
+#       Supported:
+#
+#       - Packages
+#       - Inheritance via "use base" and "@ISA =".
+#       - Functions
+#       - Variables
+#
+#       Not supported yet:
+#
+#       - Constants
+#
+###############################################################################
+
+# 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::Languages::Perl;
+
+use base 'NaturalDocs::Languages::Advanced';
+
+
+#
+#   array: hereDocTerminators
+#   An array of active Here Doc terminators, or an empty array if not active.  Each entry is an arrayref of tokens.  The entries
+#   must appear in the order they must appear in the source.
+#
+my @hereDocTerminators;
+
+
+
+###############################################################################
+# Group: Interface Functions
+
+
+#
+#   Function: PackageSeparator
+#   Returns the package separator symbol.
+#
+sub PackageSeparator
+    {  return '::';  };
+
+#
+#   Function: EnumValues
+#   Returns the <EnumValuesType> that describes how the language handles enums.
+#
+sub EnumValues
+    {  return ::ENUM_GLOBAL();  };
+
+
+#
+#   Function: ParseFile
+#
+#   Parses the passed source file, sending comments acceptable for documentation to <NaturalDocs::Parser->OnComment()>.
+#
+#   Parameters:
+#
+#       sourceFile - The name of the source file to parse.
+#       topicList - A reference to the list of <NaturalDocs::Parser::ParsedTopics> being built by the file.
+#
+#   Returns:
+#
+#       The array ( autoTopics, scopeRecord ).
+#
+#       autoTopics - An arrayref of automatically generated topics from the file, or undef if none.
+#       scopeRecord - An arrayref of <NaturalDocs::Languages::Advanced::ScopeChanges>, or undef if none.
+#
+sub ParseFile #(sourceFile, topicsList)
+    {
+    my ($self, $sourceFile, $topicsList) = @_;
+
+    @hereDocTerminators = ( );
+
+    # The regular block comment symbols are undef because they're all potentially JavaDoc comments.  PreprocessFile() will
+    # handle translating things like =begin naturaldocs and =begin javadoc to =begin nd.
+    $self->ParseForCommentsAndTokens($sourceFile, [ '#' ], undef, [ '##' ], [ '=begin nd', '=end nd' ]);
+
+    my $tokens = $self->Tokens();
+    my $index = 0;
+    my $lineNumber = 1;
+
+    while ($index < scalar @$tokens)
+        {
+        if ($self->TryToSkipWhitespace(\$index, \$lineNumber) ||
+            $self->TryToGetPackage(\$index, \$lineNumber) ||
+            $self->TryToGetBase(\$index, \$lineNumber) ||
+            $self->TryToGetFunction(\$index, \$lineNumber) ||
+            $self->TryToGetVariable(\$index, \$lineNumber) )
+            {
+            # The functions above will handle everything.
+            }
+
+        elsif ($tokens->[$index] eq '{')
+            {
+            $self->StartScope('}', $lineNumber, undef);
+            $index++;
+            }
+
+        elsif ($tokens->[$index] eq '}')
+            {
+            if ($self->ClosingScopeSymbol() eq '}')
+                {  $self->EndScope($lineNumber);  };
+
+            $index++;
+            }
+
+        elsif (lc($tokens->[$index]) eq 'eval')
+            {
+            # We want to skip the token in this case instead of letting it fall to SkipRestOfStatement.  This allows evals with braces
+            # to be treated like normal floating braces.
+            $index++;
+            }
+
+        else
+            {
+            $self->SkipRestOfStatement(\$index, \$lineNumber);
+            };
+        };
+
+
+    # Don't need to keep these around.
+    $self->ClearTokens();
+
+    return ( $self->AutoTopics(), $self->ScopeRecord() );
+    };
+
+
+#
+#   Function: PreprocessFile
+#
+#   Overridden to support "=begin nd" and similar.
+#
+#   - "=begin [nd|naturaldocs|natural docs|jd|javadoc|java doc]" all translate to "=begin nd".
+#   - "=[nd|naturaldocs|natural docs]" also translate to "=begin nd".
+#   - "=end [nd|naturaldocs|natural docs|jd|javadoc]" all translate to "=end nd".
+#   - "=cut" from a ND block translates into "=end nd", but the next line will be altered to begin with "(NDPODBREAK)".  This is
+#     so if there is POD leading into ND which ends with a cut, the parser can still end the original POD because the end ND line
+#     would have been removed.  Remember, <NaturalDocs::Languages::Advanced->ParseForCommentsAndTokens()> removes
+#     Natural Docs-worthy comments to save parsing time.
+#   - "=pod begin nd" and "=pod end nd" are supported for compatibility with ND 1.32 and earlier, even though the syntax is a
+#     mistake.
+#   - It also supports the wrong plural forms, so naturaldoc/natural doc/javadocs/java docs will work.
+#
+sub PreprocessFile #(lines)
+    {
+    my ($self, $lines) = @_;
+
+    my $inNDPOD = 0;
+    my $mustBreakPOD = 0;
+
+    for (my $i = 0; $i < scalar @$lines; $i++)
+        {
+        if ($lines->[$i] =~ /^\=(?:(?:pod[ \t]+)?begin[ \t]+)?(?:nd|natural[ \t]*docs?|jd|java[ \t]*docs?)[ \t]*$/i)
+            {
+            $lines->[$i] = '=begin nd';
+            $inNDPOD = 1;
+            $mustBreakPOD = 0;
+            }
+        elsif ($lines->[$i] =~ /^\=(?:pod[ \t]+)end[ \t]+(?:nd|natural[ \t]*docs?|jd|javadocs?)[ \t]*$/i)
+            {
+            $lines->[$i] = '=end nd';
+            $inNDPOD = 0;
+            $mustBreakPOD = 0;
+            }
+        elsif ($lines->[$i] =~ /^\=cut[ \t]*$/i)
+            {
+            if ($inNDPOD)
+                {
+                $lines->[$i] = '=end nd';
+                $inNDPOD = 0;
+                $mustBreakPOD = 1;
+                };
+            }
+        elsif ($mustBreakPOD)
+            {
+            $lines->[$i] = '(NDPODBREAK)' . $lines->[$i];
+            $mustBreakPOD = 0;
+            };
+        };
+    };
+
+
+
+###############################################################################
+# Group: Statement Parsing Functions
+# All functions here assume that the current position is at the beginning of a statement.
+#
+# Note for developers: I am well aware that the code in these functions do not check if we're past the end of the tokens as
+# often as it should.  We're making use of the fact that Perl will always return undef in these cases to keep the code simpler.
+
+
+#
+#   Function: TryToGetPackage
+#
+#   Determines whether the position is at a package declaration statement, and if so, generates a topic for it, skips it, and
+#   returns true.
+#
+sub TryToGetPackage #(indexRef, lineNumberRef)
+    {
+    my ($self, $indexRef, $lineNumberRef) = @_;
+    my $tokens = $self->Tokens();
+
+    if (lc($tokens->[$$indexRef]) eq 'package')
+        {
+        my $index = $$indexRef + 1;
+        my $lineNumber = $$lineNumberRef;
+
+        if (!$self->TryToSkipWhitespace(\$index, \$lineNumber))
+            {  return undef;  };
+
+        my $name;
+
+        while ($tokens->[$index] =~ /^[a-z_\:]/i)
+            {
+            $name .= $tokens->[$index];
+            $index++;
+            };
+
+        if (!defined $name)
+            {  return undef;  };
+
+        my $autoTopic = NaturalDocs::Parser::ParsedTopic->New(::TOPIC_CLASS(), $name,
+                                                                                             undef, undef,
+                                                                                             undef,
+                                                                                             undef, undef, $$lineNumberRef);
+        $self->AddAutoTopic($autoTopic);
+
+        NaturalDocs::Parser->OnClass($autoTopic->Symbol());
+
+        $self->SetPackage($autoTopic->Symbol(), $$lineNumberRef);
+
+        $$indexRef = $index;
+        $$lineNumberRef = $lineNumber;
+        $self->SkipRestOfStatement($indexRef, $lineNumberRef);
+
+        return 1;
+        };
+
+    return undef;
+    };
+
+
+#
+#   Function: TryToGetBase
+#
+#   Determines whether the position is at a package base declaration statement, and if so, calls
+#   <NaturalDocs::Parser->OnClassParent()>.
+#
+#   Supported Syntaxes:
+#
+#   > use base [list of strings]
+#   > @ISA = [list of strings]
+#   > @[package]::ISA = [list of strings]
+#   > our @ISA = [list of strings]
+#
+sub TryToGetBase #(indexRef, lineNumberRef)
+    {
+    my ($self, $indexRef, $lineNumberRef) = @_;
+    my $tokens = $self->Tokens();
+
+    my ($index, $lineNumber, $class, $parents);
+
+    if (lc($tokens->[$$indexRef]) eq 'use')
+        {
+        $index = $$indexRef + 1;
+        $lineNumber = $$lineNumberRef;
+
+        if (!$self->TryToSkipWhitespace(\$index, \$lineNumber) ||
+           lc($tokens->[$index]) ne 'base')
+            {  return undef;  }
+
+        $index++;
+        $self->TryToSkipWhitespace(\$index, \$lineNumber);
+
+        $parents = $self->TryToGetListOfStrings(\$index, \$lineNumber);
+        }
+
+    else
+        {
+        $index = $$indexRef;
+        $lineNumber = $$lineNumberRef;
+
+        if (lc($tokens->[$index]) eq 'our')
+            {
+            $index++;
+            $self->TryToSkipWhitespace(\$index, \$lineNumber);
+            };
+
+        if ($tokens->[$index] eq '@')
+            {
+            $index++;
+
+            while ($index < scalar @$tokens)
+                {
+                if ($tokens->[$index] eq 'ISA')
+                    {
+                    $index++;
+                    $self->TryToSkipWhitespace(\$index, \$lineNumber);
+
+                    if ($tokens->[$index] eq '=')
+                        {
+                        $index++;
+                        $self->TryToSkipWhitespace(\$index, \$lineNumber);
+
+                        $parents = $self->TryToGetListOfStrings(\$index, \$lineNumber);
+                        }
+                    else
+                        {  last;  };
+                    }
+
+                # If token isn't ISA...
+                elsif ($tokens->[$index] =~ /^[a-z0-9_:]/i)
+                    {
+                    $class .= $tokens->[$index];
+                    $index++;
+                    }
+                else
+                    {  last;  };
+                };
+            };
+        };
+
+    if (defined $parents)
+        {
+        if (defined $class)
+            {
+            $class =~ s/::$//;
+            my @classIdentifiers = split(/::/, $class);
+            $class = NaturalDocs::SymbolString->Join(@classIdentifiers);
+            }
+        else
+            {  $class = $self->CurrentScope();  };
+
+        foreach my $parent (@$parents)
+            {
+            my @parentIdentifiers = split(/::/, $parent);
+            my $parentSymbol = NaturalDocs::SymbolString->Join(@parentIdentifiers);
+
+            NaturalDocs::Parser->OnClassParent($class, $parentSymbol, undef, undef, ::RESOLVE_ABSOLUTE());
+            };
+
+        $$indexRef = $index;
+        $$lineNumberRef = $lineNumber;
+        $self->SkipRestOfStatement($indexRef, $lineNumberRef);
+
+        return 1;
+        }
+    else
+        {  return undef;  };
+    };
+
+
+#
+#   Function: TryToGetFunction
+#
+#   Determines whether the position is at a function declaration statement, and if so, generates a topic for it, skips it, and
+#   returns true.
+#
+sub TryToGetFunction #(indexRef, lineNumberRef)
+    {
+    my ($self, $indexRef, $lineNumberRef) = @_;
+    my $tokens = $self->Tokens();
+
+    if ( lc($tokens->[$$indexRef]) eq 'sub')
+        {
+        my $prototypeStart = $$indexRef;
+        my $prototypeStartLine = $$lineNumberRef;
+        my $prototypeEnd = $$indexRef + 1;
+        my $prototypeEndLine = $$lineNumberRef;
+
+        if ( !$self->TryToSkipWhitespace(\$prototypeEnd, \$prototypeEndLine) ||
+             $tokens->[$prototypeEnd] !~ /^[a-z_]/i )
+            {  return undef;  };
+
+        my $name = $tokens->[$prototypeEnd];
+        $prototypeEnd++;
+
+        # We parsed 'sub [name]'.  Now keep going until we find a semicolon or a brace.
+
+        for (;;)
+            {
+            if ($prototypeEnd >= scalar @$tokens)
+                {  return undef;  }
+
+            # End if we find a semicolon, since it means we found a predeclaration rather than an actual function.
+            elsif ($tokens->[$prototypeEnd] eq ';')
+                {  return undef;  }
+
+            elsif ($tokens->[$prototypeEnd] eq '{')
+                {
+                # Found it!
+
+                my $prototype = $self->NormalizePrototype( $self->CreateString($prototypeStart, $prototypeEnd) );
+
+                $self->AddAutoTopic(NaturalDocs::Parser::ParsedTopic->New(::TOPIC_FUNCTION(), $name,
+                                                                                                          $self->CurrentScope(), undef,
+                                                                                                          $prototype,
+                                                                                                          undef, undef, $prototypeStartLine));
+
+                $$indexRef = $prototypeEnd;
+                $$lineNumberRef = $prototypeEndLine;
+
+                $self->SkipRestOfStatement($indexRef, $lineNumberRef);
+
+                return 1;
+                }
+
+            else
+                {  $self->GenericSkip(\$prototypeEnd, \$prototypeEndLine, 0, 1);  };
+            };
+        }
+    else
+        {  return undef;  };
+    };
+
+
+#
+#   Function: TryToGetVariable
+#
+#   Determines if the position is at a variable declaration statement, and if so, generates a topic for it, skips it, and returns
+#   true.
+#
+#   Supported Syntaxes:
+#
+#   - Supports variables declared with "my", "our", and "local".
+#   - Supports multiple declarations in one statement, such as "my ($x, $y);".
+#   - Supports types and attributes.
+#
+sub TryToGetVariable #(indexRef, lineNumberRef)
+    {
+    my ($self, $indexRef, $lineNumberRef) = @_;
+    my $tokens = $self->Tokens();
+
+    my $firstToken = lc( $tokens->[$$indexRef] );
+
+    if ($firstToken eq 'my' || $firstToken eq 'our' || $firstToken eq 'local')
+        {
+        my $prototypeStart = $$indexRef;
+        my $prototypeStartLine = $$lineNumberRef;
+        my $prototypeEnd = $$indexRef + 1;
+        my $prototypeEndLine = $$lineNumberRef;
+
+        $self->TryToSkipWhitespace(\$prototypeEnd, \$prototypeEndLine);
+
+
+        # Get the type if present.
+
+        my $type;
+
+        if ($tokens->[$prototypeEnd] =~ /^[a-z\:]/i)
+            {
+            do
+                {
+                $type .= $tokens->[$prototypeEnd];
+                $prototypeEnd++;
+                }
+            while ($tokens->[$prototypeEnd] =~ /^[a-z\:]/i);
+
+            if (!$self->TryToSkipWhitespace(\$prototypeEnd, \$prototypeEndLine))
+                {  return undef;  };
+            };
+
+
+        # Get the name, or possibly names.
+
+        if ($tokens->[$prototypeEnd] eq '(')
+            {
+            # If there's multiple variables, we'll need to build a custom prototype for each one.  $firstToken already has the
+            # declaring word.  We're going to store each name in @names, and we're going to use $prototypeStart and
+            # $prototypeEnd to capture any properties appearing after the list.
+
+            my $name;
+            my @names;
+            my $hasComma = 0;
+
+            $prototypeStart = $prototypeEnd + 1;
+            $prototypeStartLine = $prototypeEndLine;
+
+            for (;;)
+                {
+                $self->TryToSkipWhitespace(\$prototypeStart, \$prototypeStartLine);
+
+                $name = $self->TryToGetVariableName(\$prototypeStart, \$prototypeStartLine);
+
+                if (!defined $name)
+                    {  return undef;  };
+
+                push @names, $name;
+
+                $self->TryToSkipWhitespace(\$prototypeStart, \$prototypeStartLine);
+
+                # We can have multiple commas in a row.  We can also have trailing commas.  However, the parenthesis must
+                # not start with a comma or be empty, hence this logic does not appear earlier.
+                while ($tokens->[$prototypeStart] eq ',')
+                    {
+                    $prototypeStart++;
+                    $self->TryToSkipWhitespace(\$prototypeStart, \$prototypeStartLine);
+
+                    $hasComma = 1;
+                    }
+
+                if ($tokens->[$prototypeStart] eq ')')
+                    {
+                    $prototypeStart++;
+                    last;
+                    }
+                elsif (!$hasComma)
+                    {  return undef;  };
+                };
+
+
+            # Now find the end of the prototype.
+
+            $prototypeEnd = $prototypeStart;
+            $prototypeEndLine = $prototypeStartLine;
+
+            while ($prototypeEnd < scalar @$tokens &&
+                     $tokens->[$prototypeEnd] !~ /^[\;\=]/)
+                {
+                $prototypeEnd++;
+                };
+
+
+            my $prototypePrefix = $firstToken . ' ';
+            if (defined $type)
+                {  $prototypePrefix .= $type . ' ';  };
+
+            my $prototypeSuffix = ' ' . $self->CreateString($prototypeStart, $prototypeEnd);
+
+            foreach $name (@names)
+                {
+                my $prototype = $self->NormalizePrototype( $prototypePrefix . $name . $prototypeSuffix );
+
+                $self->AddAutoTopic(NaturalDocs::Parser::ParsedTopic->New(::TOPIC_VARIABLE(), $name,
+                                                                                                           $self->CurrentScope(), undef,
+                                                                                                           $prototype,
+                                                                                                           undef, undef, $prototypeStartLine));
+                };
+
+            $self->SkipRestOfStatement(\$prototypeEnd, \$prototypeEndLine);
+
+            $$indexRef = $prototypeEnd;
+            $$lineNumberRef = $prototypeEndLine;
+            }
+
+        else # no parenthesis
+            {
+            my $name = $self->TryToGetVariableName(\$prototypeEnd, \$prototypeEndLine);
+
+            if (!defined $name)
+                {  return undef;  };
+
+            while ($prototypeEnd < scalar @$tokens &&
+                     $tokens->[$prototypeEnd] !~ /^[\;\=]/)
+                {
+                $prototypeEnd++;
+                };
+
+            my $prototype = $self->NormalizePrototype( $self->CreateString($prototypeStart, $prototypeEnd) );
+
+            $self->AddAutoTopic(NaturalDocs::Parser::ParsedTopic->New(::TOPIC_VARIABLE(), $name,
+                                                                                                       $self->CurrentScope(), undef,
+                                                                                                       $prototype,
+                                                                                                       undef, undef, $prototypeStartLine));
+
+            $self->SkipRestOfStatement(\$prototypeEnd, \$prototypeEndLine);
+
+            $$indexRef = $prototypeEnd;
+            $$lineNumberRef = $prototypeEndLine;
+            };
+
+        return 1;
+        }
+    else
+        {  return undef;  };
+    };
+
+
+#
+#   Function: TryToGetVariableName
+#
+#   Determines if the position is at a variable name, and if so, skips it and returns the name.
+#
+sub TryToGetVariableName #(indexRef, lineNumberRef)
+    {
+    my ($self, $indexRef, $lineNumberRef) = @_;
+    my $tokens = $self->Tokens();
+
+    my $name;
+
+    if ($tokens->[$$indexRef] =~ /^[\$\@\%\*]/)
+        {
+        $name .= $tokens->[$$indexRef];
+        $$indexRef++;
+
+        $self->TryToSkipWhitespace($indexRef, $lineNumberRef);
+
+        if ($tokens->[$$indexRef] =~ /^[a-z_]/i)
+            {
+            $name .= $tokens->[$$indexRef];
+            $$indexRef++;
+            }
+        else
+            {  return undef;  };
+        };
+
+    return $name;
+    };
+
+
+#
+#   Function: TryToGetListOfStrings
+#
+#   Attempts to retrieve a list of strings from the current position.  Returns an arrayref of them if any are found, or undef if none.
+#   It stops the moment it reaches a non-string, so "string1, variable, string2" will only return string1.
+#
+#   Supported Syntaxes:
+#
+#   - Supports parenthesis.
+#   - Supports all string forms supported by <TryToSkipString()>.
+#   - Supports qw() string arrays.
+#
+sub TryToGetListOfStrings #(indexRef, lineNumberRef)
+    {
+    my ($self, $indexRef, $lineNumberRef) = @_;
+    my $tokens = $self->Tokens();
+
+    my $parenthesis = 0;
+    my $strings;
+
+    while ($$indexRef < scalar @$tokens)
+        {
+        # We'll tolerate parenthesis.
+        if ($tokens->[$$indexRef] eq '(')
+            {
+            $$indexRef++;
+            $parenthesis++;
+            }
+        elsif ($tokens->[$$indexRef] eq ')')
+            {
+            if ($parenthesis == 0)
+                {  last;  };
+
+            $$indexRef++;
+            $parenthesis--;
+            }
+        elsif ($tokens->[$$indexRef] eq ',')
+            {
+            $$indexRef++;
+            }
+        else
+            {
+            my ($startContent, $endContent);
+            my $symbolIndex = $$indexRef;
+
+            if ($self->TryToSkipString($indexRef, $lineNumberRef, \$startContent, \$endContent))
+                {
+                my $content = $self->CreateString($startContent, $endContent);
+
+                if (!defined $strings)
+                    {  $strings = [ ];  };
+
+                if (lc($tokens->[$symbolIndex]) eq 'qw')
+                    {
+                    $content =~ tr/ \t\n/ /s;
+                    $content =~ s/^ //;
+
+                    my @qwStrings = split(/ /, $content);
+
+                    push @$strings, @qwStrings;
+                    }
+                else
+                    {
+                    push @$strings, $content;
+                    };
+                }
+            else
+                {  last;  };
+            };
+
+        $self->TryToSkipWhitespace($indexRef, $lineNumberRef);
+        };
+
+    return $strings;
+    };
+
+
+###############################################################################
+# Group: Low Level Parsing Functions
+
+
+#
+#   Function: GenericSkip
+#
+#   Advances the position one place through general code.
+#
+#   - If the position is on a comment or string, it will skip it completely.
+#   - If the position is on an opening symbol, it will skip until the past the closing symbol.
+#   - If the position is on a regexp or quote-like operator, it will skip it completely.
+#   - If the position is on a backslash, it will skip it and the following token.
+#   - If the position is on whitespace (including comments), it will skip it completely.
+#   - Otherwise it skips one token.
+#
+#   Parameters:
+#
+#       indexRef - A reference to the current index.
+#       lineNumberRef - A reference to the current line number.
+#       noRegExps - If set, does not test for regular expressions.
+#
+sub GenericSkip #(indexRef, lineNumberRef, noRegExps)
+    {
+    my ($self, $indexRef, $lineNumberRef, $noRegExps, $allowStringedClosingParens) = @_;
+    my $tokens = $self->Tokens();
+
+    if ($tokens->[$$indexRef] eq "\\" && $$indexRef + 1 < scalar @$tokens && $tokens->[$$indexRef+1] ne "\n")
+        {  $$indexRef += 2;  }
+
+    # Note that we don't want to count backslashed ()[]{} since they could be in regexps.  Also, ()[] are valid variable names
+    # when preceded by a string.
+
+    # We can ignore the scope stack because we're just skipping everything without parsing, and we need recursion anyway.
+    elsif ($tokens->[$$indexRef] eq '{' && !$self->IsBackslashed($$indexRef))
+        {
+        $$indexRef++;
+        $self->GenericSkipUntilAfter($indexRef, $lineNumberRef, '}', $noRegExps, $allowStringedClosingParens);
+        }
+    elsif ($tokens->[$$indexRef] eq '(' && !$self->IsBackslashed($$indexRef) && !$self->IsStringed($$indexRef))
+        {
+        # Temporarily allow stringed closing parenthesis if it looks like we're in an anonymous function declaration with Perl's
+        # cheap version of prototypes, such as "my $_declare = sub($) {}".
+        my $tempAllowStringedClosingParens = $allowStringedClosingParens;
+        if (!$allowStringedClosingParens)
+        	{
+        	my $tempIndex = $$indexRef - 1;
+        	if ($tempIndex >= 0 && $tokens->[$tempIndex] =~ /^[ \t]/)
+        		{  $tempIndex--;  }
+        	if ($tempIndex >= 0 && $tokens->[$tempIndex] eq 'sub')
+        		{  $tempAllowStringedClosingParens = 1;  }
+        	}
+
+        $$indexRef++;
+
+        do
+            {  $self->GenericSkipUntilAfter($indexRef, $lineNumberRef, ')', $noRegExps, $tempAllowStringedClosingParens);  }
+        while ($$indexRef < scalar @$tokens && $self->IsStringed($$indexRef - 1) && !$tempAllowStringedClosingParens);
+        }
+    elsif ($tokens->[$$indexRef] eq '[' && !$self->IsBackslashed($$indexRef) && !$self->IsStringed($$indexRef))
+        {
+        $$indexRef++;
+
+        do
+            {  $self->GenericSkipUntilAfter($indexRef, $lineNumberRef, ']', $noRegExps, $allowStringedClosingParens);  }
+        while ($$indexRef < scalar @$tokens && $self->IsStringed($$indexRef - 1));
+        }
+
+    elsif ($self->TryToSkipWhitespace($indexRef, $lineNumberRef) ||
+            $self->TryToSkipString($indexRef, $lineNumberRef) ||
+            $self->TryToSkipHereDocDeclaration($indexRef, $lineNumberRef) ||
+            (!$noRegExps && $self->TryToSkipRegexp($indexRef, $lineNumberRef) ) )
+        {
+        }
+
+    else
+        {  $$indexRef++;  };
+    };
+
+
+#
+#   Function: GenericSkipUntilAfter
+#
+#   Advances the position via <GenericSkip()> until a specific token is reached and passed.
+#
+sub GenericSkipUntilAfter #(indexRef, lineNumberRef, token, noRegExps, allowStringedClosingParens)
+    {
+    my ($self, $indexRef, $lineNumberRef, $token, $noRegExps, $allowStringedClosingParens) = @_;
+    my $tokens = $self->Tokens();
+
+    while ($$indexRef < scalar @$tokens && $tokens->[$$indexRef] ne $token)
+        {  $self->GenericSkip($indexRef, $lineNumberRef, $noRegExps, $allowStringedClosingParens);  };
+
+    if ($tokens->[$$indexRef] eq "\n")
+        {  $$lineNumberRef++;  };
+    $$indexRef++;
+    };
+
+
+#
+#   Function: GenericRegexpSkip
+#
+#   Advances the position one place through regexp code.
+#
+#   - If the position is on an opening symbol, it will skip until the past the closing symbol.
+#   - If the position is on a backslash, it will skip it and the following token.
+#   - If the position is on whitespace (not including comments), it will skip it completely.
+#   - Otherwise it skips one token.
+#
+#   Also differs from <GenericSkip()> in that the parenthesis in $( and $) do count against the scope, where they wouldn't
+#   normally.
+#
+#   Parameters:
+#
+#       indexRef - A reference to the current index.
+#       lineNumberRef - A reference to the current line number.
+#       inBrackets - Whether we're in brackets or not.  If true, we don't care about matching braces and parenthesis.
+#
+sub GenericRegexpSkip #(indexRef, lineNumberRef, inBrackets)
+    {
+    my ($self, $indexRef, $lineNumberRef, $inBrackets) = @_;
+    my $tokens = $self->Tokens();
+
+    if ($tokens->[$$indexRef] eq "\\" && $$indexRef + 1 < scalar @$tokens && $tokens->[$$indexRef+1] ne "\n")
+        {  $$indexRef += 2;  }
+
+    # We can ignore the scope stack because we're just skipping everything without parsing, and we need recursion anyway.
+    elsif ($tokens->[$$indexRef] eq '{' && !$self->IsBackslashed($$indexRef) && !$inBrackets)
+        {
+        $$indexRef++;
+        $self->GenericRegexpSkipUntilAfter($indexRef, $lineNumberRef, '}');
+        }
+    elsif ($tokens->[$$indexRef] eq '(' && !$self->IsBackslashed($$indexRef) && !$inBrackets)
+        {
+        $$indexRef++;
+        $self->GenericRegexpSkipUntilAfter($indexRef, $lineNumberRef, ')');
+        }
+    elsif ($tokens->[$$indexRef] eq '[' && !$self->IsBackslashed($$indexRef) && !$self->IsStringed($$indexRef))
+        {
+        $$indexRef++;
+
+        do
+            {  $self->GenericRegexpSkipUntilAfter($indexRef, $lineNumberRef, ']');  }
+        while ($$indexRef < scalar @$tokens && $self->IsStringed($$indexRef - 1));
+        }
+
+    elsif ($tokens->[$$indexRef] eq "\n")
+        {
+        $$lineNumberRef++;
+        $$indexRef++;
+        }
+
+    else
+        {  $$indexRef++;  };
+    };
+
+
+#
+#   Function: GenericRegexpSkipUntilAfter
+#
+#   Advances the position via <GenericRegexpSkip()> until a specific token is reached and passed.
+#
+sub GenericRegexpSkipUntilAfter #(indexRef, lineNumberRef, token)
+    {
+    my ($self, $indexRef, $lineNumberRef, $token) = @_;
+    my $tokens = $self->Tokens();
+
+    my $inBrackets = ( $token eq ']' );
+
+    while ($$indexRef < scalar @$tokens && $tokens->[$$indexRef] ne $token)
+        {  $self->GenericRegexpSkip($indexRef, $lineNumberRef, $inBrackets);  };
+
+    if ($tokens->[$$indexRef] eq "\n")
+        {  $$lineNumberRef++;  };
+    $$indexRef++;
+    };
+
+
+#
+#   Function: SkipRestOfStatement
+#
+#   Advances the position via <GenericSkip()> until after the end of the current statement, which is defined as a semicolon or
+#   a brace group.  Of course, either of those appearing inside parenthesis, a nested brace group, etc. don't count.
+#
+sub SkipRestOfStatement #(indexRef, lineNumberRef)
+    {
+    my ($self, $indexRef, $lineNumberRef) = @_;
+    my $tokens = $self->Tokens();
+
+    while ($$indexRef < scalar @$tokens &&
+             $tokens->[$$indexRef] ne ';' &&
+             !($tokens->[$$indexRef] eq '{' && !$self->IsStringed($$indexRef)) )
+        {
+        $self->GenericSkip($indexRef, $lineNumberRef);
+        };
+
+    if ($tokens->[$$indexRef] eq ';')
+        {  $$indexRef++;  }
+    elsif ($tokens->[$$indexRef] eq '{')
+        {  $self->GenericSkip($indexRef, $lineNumberRef);  };
+    };
+
+
+#
+#   Function: TryToSkipWhitespace
+#
+#   If the current position is on whitespace it skips them and returns true.  If there are a number of these in a row, it skips them
+#   all.
+#
+#   Supported Syntax:
+#
+#       - Whitespace
+#       - Line break
+#       - All comment forms supported by <TryToSkipComment()>
+#       - Here Doc content
+#
+sub TryToSkipWhitespace #(indexRef, lineNumberRef)
+    {
+    my ($self, $indexRef, $lineNumberRef) = @_;
+    my $tokens = $self->Tokens();
+
+    my $result;
+
+    while ($$indexRef < scalar @$tokens)
+        {
+        if ($self->TryToSkipHereDocContent($indexRef, $lineNumberRef) ||
+            $self->TryToSkipComment($indexRef, $lineNumberRef))
+            {
+            $result = 1;
+            }
+        elsif ($tokens->[$$indexRef] =~ /^[ \t]/)
+            {
+            $$indexRef++;
+            $result = 1;
+            }
+        elsif ($tokens->[$$indexRef] eq "\n")
+            {
+            $$indexRef++;
+            $$lineNumberRef++;
+            $result = 1;
+            }
+        else
+            {  last;  };
+        };
+
+    return $result;
+    };
+
+
+#
+#   Function: TryToSkipComment
+#   If the current position is on a comment, skip past it and return true.
+#
+sub TryToSkipComment #(indexRef, lineNumberRef)
+    {
+    my ($self, $indexRef, $lineNumberRef) = @_;
+
+    return ( $self->TryToSkipLineComment($indexRef, $lineNumberRef) ||
+                $self->TryToSkipPODComment($indexRef, $lineNumberRef) );
+    };
+
+
+#
+#   Function: TryToSkipLineComment
+#   If the current position is on a line comment symbol, skip past it and return true.
+#
+sub TryToSkipLineComment #(indexRef, lineNumberRef)
+    {
+    my ($self, $indexRef, $lineNumberRef) = @_;
+    my $tokens = $self->Tokens();
+
+    # Note that $#var is not a comment.
+    if ($tokens->[$$indexRef] eq '#' && !$self->IsStringed($$indexRef))
+        {
+        $self->SkipRestOfLine($indexRef, $lineNumberRef);
+        return 1;
+        }
+    else
+        {  return undef;  };
+    };
+
+
+#
+#   Function: TryToSkipPODComment
+#   If the current position is on a POD comment symbol, skip past it and return true.
+#
+sub TryToSkipPODComment #(indexRef, lineNumberRef)
+    {
+    my ($self, $indexRef, $lineNumberRef) = @_;
+    my $tokens = $self->Tokens();
+
+    # Note that whitespace is not allowed before the equals sign.  It must directly start a line.
+    if ($tokens->[$$indexRef] eq '=' &&
+        ( $$indexRef == 0 || $tokens->[$$indexRef - 1] eq "\n" ) &&
+        $tokens->[$$indexRef + 1] =~ /^[a-z]/i )
+        {
+        # Skip until =cut or (NDPODBREAK).  Note that it's theoretically possible for =cut to appear without a prior POD directive.
+
+        do
+            {
+            if ($tokens->[$$indexRef] eq '=' && lc( $tokens->[$$indexRef + 1] ) eq 'cut')
+                {
+                $self->SkipRestOfLine($indexRef, $lineNumberRef);
+                last;
+                }
+            elsif ($tokens->[$$indexRef] eq '(' && $$indexRef + 2 < scalar @$tokens &&
+                    $tokens->[$$indexRef+1] eq 'NDPODBREAK' && $tokens->[$$indexRef+2] eq ')')
+                {
+                $$indexRef += 3;
+                last;
+                }
+            else
+                {
+                $self->SkipRestOfLine($indexRef, $lineNumberRef);
+                };
+            }
+        while ($$indexRef < scalar @$tokens);
+
+        return 1;
+        }
+
+    # It's also possible that (NDPODBREAK) will appear without any opening pod statement because "=begin nd" and "=cut" will
+    # still result in one.  We need to pick off the stray (NDPODBREAK).
+    elsif ($tokens->[$$indexRef] eq '(' && $$indexRef + 2 < scalar @$tokens &&
+            $tokens->[$$indexRef+1] eq 'NDPODBREAK' && $tokens->[$$indexRef+2] eq ')')
+        {
+        $$indexRef += 3;
+        return 1;
+        }
+
+    else
+        {  return undef;  };
+    };
+
+
+#
+#   Function: TryToSkipString
+#   If the current position is on a string delimiter, skip past the string and return true.
+#
+#   Parameters:
+#
+#       indexRef - A reference to the index of the position to start at.
+#       lineNumberRef - A reference to the line number of the position.
+#       startContentIndexRef - A reference to the variable in which to store the index of the first content token.  May be undef.
+#       endContentIndexRef - A reference to the variable in which to store the index of the end of the content, which is one past
+#                                        the last content token.  may be undef.
+#
+#   Returns:
+#
+#       Whether the position was at a string.  The index, line number, and content index variabls will only be changed if true.
+#
+#   Syntax Support:
+#
+#       - Supports quotes, apostrophes, backticks, q(), qq(), qx(), and qw().
+#       - All symbols are supported for the letter forms.
+#
+sub TryToSkipString #(indexRef, lineNumberRef, startContentIndexRef, endContentIndexRef)
+    {
+    my ($self, $indexRef, $lineNumberRef, $startContentIndexRef, $endContentIndexRef) = @_;
+    my $tokens = $self->Tokens();
+
+    # The three string delimiters.  All three are Perl variables when preceded by a dollar sign.
+    if (!$self->IsStringed($$indexRef) &&
+        ( $self->SUPER::TryToSkipString($indexRef, $lineNumberRef, '\'', '\'', $startContentIndexRef, $endContentIndexRef) ||
+          $self->SUPER::TryToSkipString($indexRef, $lineNumberRef, '"', '"', $startContentIndexRef, $endContentIndexRef) ||
+          $self->SUPER::TryToSkipString($indexRef, $lineNumberRef, '`', '`', $startContentIndexRef, $endContentIndexRef) ) )
+        {
+        return 1;
+        }
+    elsif ($tokens->[$$indexRef] =~ /^(?:q|qq|qx|qw)$/i &&
+            ($$indexRef == 0 || $tokens->[$$indexRef - 1] !~ /^[\$\%\@\*]$/))
+        {
+        $$indexRef++;
+
+        $self->TryToSkipWhitespace($indexRef, $lineNumberRef);
+
+        my $openingSymbol = $tokens->[$$indexRef];
+        my $closingSymbol;
+
+        if ($openingSymbol eq '{')
+            {  $closingSymbol = '}';  }
+        elsif ($openingSymbol eq '(')
+            {  $closingSymbol = ')';  }
+        elsif ($openingSymbol eq '[')
+            {  $closingSymbol = ']';  }
+        elsif ($openingSymbol eq '<')
+            {  $closingSymbol = '>';  }
+        else
+            {  $closingSymbol = $openingSymbol;  };
+
+        $self->SUPER::TryToSkipString($indexRef, $lineNumberRef, $openingSymbol, $closingSymbol,
+                                                      $startContentIndexRef, $endContentIndexRef);
+
+        return 1;
+        }
+    else
+        {  return undef;  };
+    };
+
+
+#
+#   Function: TryToSkipHereDocDeclaration
+#
+#   If the current position is on a Here Doc declaration, add its terminators to <hereDocTerminators> and skip it.
+#
+#   Syntax Support:
+#
+#       - Supports <<EOF
+#       - Supports << "String" with all string forms supported by <TryToSkipString()>.
+#
+sub TryToSkipHereDocDeclaration #(indexRef, lineNumberRef)
+    {
+    my ($self, $indexRef, $lineNumberRef) = @_;
+    my $tokens = $self->Tokens();
+
+    my $index = $$indexRef;
+    my $lineNumber = $$lineNumberRef;
+
+    if ($tokens->[$index] eq '<' && $tokens->[$index + 1] eq '<')
+        {
+        $index += 2;
+        my $success;
+
+        # No whitespace allowed with the bare word.
+        if ($tokens->[$index] =~ /^[a-z0-9_]/i)
+            {
+            push @hereDocTerminators, [ $tokens->[$index] ];
+            $index++;
+            $success = 1;
+            }
+        else
+            {
+            $self->TryToSkipWhitespace(\$index, \$lineNumber);
+
+            my ($contentStart, $contentEnd);
+            if ($self->TryToSkipString(\$index, \$lineNumber, \$contentStart, \$contentEnd))
+                {
+                push @hereDocTerminators, [ @{$tokens}[$contentStart..$contentEnd - 1] ];
+                $success = 1;
+                };
+            };
+
+        if ($success)
+            {
+            $$indexRef = $index;
+            $$lineNumberRef = $lineNumber;
+
+            return 1;
+            };
+        };
+
+    return 0;
+    };
+
+
+#
+#   Function: TryToSkipHereDocContent
+#
+#   If the current position is at the beginning of a line and there are entries in <hereDocTerminators>, skips lines until all the
+#   terminators are exhausted or we reach the end of the file.
+#
+#   Returns:
+#
+#       Whether the position was on Here Doc content.
+#
+sub TryToSkipHereDocContent #(indexRef, lineNumberRef)
+    {
+    my ($self, $indexRef, $lineNumberRef) = @_;
+    my $tokens = $self->Tokens();
+
+    # We don't use IsFirstLineToken() because it really needs to be the first line token.  Whitespace is not allowed.
+    if ($$indexRef > 0 && $tokens->[$$indexRef - 1] eq "\n")
+        {
+        my $success = (scalar @hereDocTerminators > 0);
+
+        while (scalar @hereDocTerminators && $$indexRef < scalar @$tokens)
+            {
+            my $terminatorIndex = 0;
+
+            while ($hereDocTerminators[0]->[$terminatorIndex] eq $tokens->[$$indexRef])
+                {
+                $terminatorIndex++;
+                $$indexRef++;
+                };
+
+            if ($terminatorIndex == scalar @{$hereDocTerminators[0]} &&
+                ($tokens->[$$indexRef] eq "\n" || ($tokens->[$$indexRef] =~ /^[ \t]/ && $tokens->[$$indexRef + 1] eq "\n")) )
+                {
+                shift @hereDocTerminators;
+                $$indexRef++;
+                $$lineNumberRef++;
+                }
+            else
+                {  $self->SkipRestOfLine($indexRef, $lineNumberRef);  };
+            };
+
+        return $success;
+        }
+
+    else
+        {  return 0;  };
+    };
+
+
+#
+#   Function: TryToSkipRegexp
+#   If the current position is on a regular expression or a quote-like operator, skip past it and return true.
+#
+#   Syntax Support:
+#
+#       - Supports //, ??, m//, qr//, s///, tr///, and y///.
+#       - All symbols are supported for the letter forms.
+#       - ?? is *not* supported because it could cause problems with ?: statements.  The generic parser has a good chance of
+#         successfully stumbling through a regex, whereas the regex code will almost certainly see the rest of the file as part of it.
+#
+sub TryToSkipRegexp #(indexRef, lineNumberRef)
+    {
+    my ($self, $indexRef, $lineNumberRef) = @_;
+    my $tokens = $self->Tokens();
+
+    my $isRegexp;
+
+    # If it's a supported character sequence that's not a variable (ex $qr)...
+    if ($tokens->[$$indexRef] =~ /^(?:m|qr|s|tr|y)$/i &&
+         ($$indexRef == 0 || $tokens->[$$indexRef - 1] !~ /^[\$\%\@\*\-]$/) )
+        {  $isRegexp = 1;  }
+
+    elsif ($tokens->[$$indexRef] eq '/' && !$self->IsStringed($$indexRef))
+        {
+        # This is a bit of a hack.  If we find a random slash, it could be a divide operator or a bare regexp.  Find the first previous
+        # non-whitespace token and if it's text, a closing brace, or a string, assume it's a divide operator.  (Strings don't make
+        # much pratical sense there but a regexp would be impossible.)  Otherwise assume it's a regexp.
+
+        # We make a special consideration for split() appearing without parenthesis.  If the previous token is split and it's not a
+        # variable, assume it is a regexp even though it fails the above test.
+
+        my $index = $$indexRef - 1;
+
+        while ($index >= 0 && $tokens->[$index] =~ /^(?: |\t|\n)/)
+            {  $index--;  };
+
+        if ($index < 0 || $tokens->[$index] !~ /^[a-zA-Z0-9_\)\]\}\'\"\`]/ ||
+            ($tokens->[$index] =~ /^split|grep$/ && $index > 0 && $tokens->[$index-1] !~ /^[\$\%\@\*]$/) )
+            {  $isRegexp = 1;  };
+        };
+
+    if ($isRegexp)
+        {
+        my $operator = lc($tokens->[$$indexRef]);
+        my $index = $$indexRef;
+        my $lineNumber = $$lineNumberRef;
+
+        if ($operator =~ /^[\?\/]/)
+            {  $operator = 'm';  }
+        else
+            {
+            $index++;
+
+            # Believe it or not, s#...# is allowed.  We can't pass over number signs here.
+            if ($tokens->[$index] ne '#')
+                {  $self->TryToSkipWhitespace(\$index, \$lineNumber);  };
+            };
+
+        if ($tokens->[$index] =~ /^\w/)
+            {  return undef;  };
+        if ($tokens->[$index] eq '=' && $tokens->[$index+1] eq '>')
+        	{  return undef;  };
+
+        my $openingSymbol = $tokens->[$index];
+        my $closingSymbol;
+
+        if ($openingSymbol eq '{')
+            {  $closingSymbol = '}';  }
+        elsif ($openingSymbol eq '(')
+            {  $closingSymbol = ')';  }
+        elsif ($openingSymbol eq '[')
+            {  $closingSymbol = ']';  }
+        elsif ($openingSymbol eq '<')
+            {  $closingSymbol = '>';  }
+        else
+            {  $closingSymbol = $openingSymbol;  };
+
+        $index++;
+
+        $self->GenericRegexpSkipUntilAfter(\$index, \$lineNumber, $closingSymbol);
+
+        $$indexRef = $index;
+        $$lineNumberRef = $lineNumber;
+
+        if ($operator =~ /^(?:s|tr|y)$/)
+            {
+            if ($openingSymbol ne $closingSymbol)
+                {
+                $self->TryToSkipWhitespace($indexRef, $lineNumberRef);
+
+                $openingSymbol = $tokens->[$index];
+
+                if ($openingSymbol eq '{')
+                    {  $closingSymbol = '}';  }
+                elsif ($openingSymbol eq '(')
+                    {  $closingSymbol = ')';  }
+                elsif ($openingSymbol eq '[')
+                    {  $closingSymbol = ']';  }
+                elsif ($openingSymbol eq '<')
+                    {  $closingSymbol = '>';  }
+                else
+                    {  $closingSymbol = $openingSymbol;  };
+
+                $$indexRef++;
+                };
+
+            if ($operator eq 's')
+                {
+                $self->GenericSkipUntilAfter($indexRef, $lineNumberRef, $closingSymbol, 1);
+                }
+            else # ($operator eq 'tr' || $operator eq 'y')
+                {
+                while ($$indexRef < scalar @$tokens &&
+                          ($tokens->[$$indexRef] ne $closingSymbol || $self->IsBackslashed($$indexRef)) )
+                    {
+                    if ($tokens->[$$indexRef] eq "\n")
+                        {  $$lineNumberRef++;  };
+                    $$indexRef++;
+                    };
+
+                $$indexRef++;
+                };
+            };
+
+        # We want to skip any letters after the regexp.  Otherwise something like tr/a/b/s; could have the trailing s; interpreted
+        # as another regexp.  Whitespace is not allowed between the closing symbol and the letters.
+
+        if ($tokens->[$$indexRef] =~ /^[a-z]/i)
+            {  $$indexRef++;  };
+
+        return 1;
+        };
+
+    return undef;
+    };
+
+
+
+###############################################################################
+# Group: Support Functions
+
+
+#
+#   Function: IsStringed
+#
+#   Returns whether the position is after a string (dollar sign) character.  Returns false if it's preceded by two dollar signs so
+#   "if ($x == $$)" doesn't skip the closing parenthesis as stringed.
+#
+#   Parameters:
+#
+#       index - The index of the postition.
+#
+sub IsStringed #(index)
+    {
+    my ($self, $index) = @_;
+    my $tokens = $self->Tokens();
+
+    if ($index > 0 && $tokens->[$index - 1] eq '$' && !($index > 1 && $tokens->[$index - 2] eq '$'))
+        {  return 1;  }
+    else
+        {  return undef;  };
+    };
+
+
+1;

Added: trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Languages/Prototype/Parameter.pm
===================================================================
--- trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Languages/Prototype/Parameter.pm	                        (rev 0)
+++ trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Languages/Prototype/Parameter.pm	2009-07-01 15:30:00 UTC (rev 4275)
@@ -0,0 +1,87 @@
+###############################################################################
+#
+#   Class: NaturalDocs::Languages::Prototype::Parameter
+#
+###############################################################################
+#
+#   A data class for storing parsed prototype parameters.
+#
+###############################################################################
+
+# 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::Languages::Prototype::Parameter;
+
+use NaturalDocs::DefineMembers 'TYPE', 'Type()', 'SetType()',
+                                                 'TYPE_PREFIX', 'TypePrefix()', 'SetTypePrefix()',
+                                                 'NAME', 'Name()', 'SetName()',
+                                                 'NAME_PREFIX', 'NamePrefix()', 'SetNamePrefix()',
+                                                 'DEFAULT_VALUE', 'DefaultValue()', 'SetDefaultValue()',
+                                                 'DEFAULT_VALUE_PREFIX', 'DefaultValuePrefix()', 'SetDefaultValuePrefix()';
+# Dependency: New() depends on the order of these constants and that they don't inherit from another class.
+
+
+#
+#   Constants: Members
+#
+#   The object is implemented as a blessed arrayref, with the following constants as its indexes.
+#
+#       TYPE - The parameter type, if any.
+#       TYPE_PREFIX - The parameter type prefix which should be aligned separately, if any.
+#       NAME - The parameter name.
+#       NAME_PREFIX - The parameter name prefix which should be aligned separately, if any.
+#       DEFAULT_VALUE - The default value expression, if any.
+#       DEFAULT_VALUE_PREFIX - The default value prefix which should be aligned separately, if any.
+#
+
+#
+#   Function: New
+#
+#   Creates and returns a new prototype object.
+#
+#   Parameters:
+#
+#       type - The parameter type, if any.
+#       typePrefix - The parameter type prefix which should be aligned separately, if any.
+#       name - The parameter name.
+#       namePrefix - The parameter name prefix which should be aligned separately, if any.
+#       defaultValue - The default value expression, if any.
+#       defaultValuePrefix - The default value prefix which should be aligned separately, if any.
+#
+sub New #(type, typePrefix, name, namePrefix, defaultValue, defaultValuePrefix)
+    {
+    my ($package, @params) = @_;
+
+    # Dependency: This depends on the order of the parameters being the same as the order of the constants, and that the
+    # constants don't inherit from another class.
+
+    my $object = [ @params ];
+    bless $object, $package;
+
+    return $object;
+    };
+
+
+#
+#   Functions: Members
+#
+#   Type - The parameter type, if any.
+#   SetType - Replaces the parameter type.
+#   TypePrefix - The parameter type prefix, which should be aligned separately, if any.
+#   SetTypePrefix - Replaces the parameter type prefix.
+#   Name - The parameter name.
+#   SetName - Replaces the parameter name.
+#   NamePrefix - The parameter name prefix, which should be aligned separately, if any.
+#   SetNamePrefix - Replaces the parameter name prefix.
+#   DefaultValue - The default value expression, if any.
+#   SetDefaultValue - Replaces the default value expression.
+#   DefaultValuePrefix - The default value prefix, which should be aligned separately, if any.
+#   SetDefaultValuePrefix - Replaces the default value prefix.
+#
+
+
+1;

Added: trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Languages/Prototype.pm
===================================================================
--- trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Languages/Prototype.pm	                        (rev 0)
+++ trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Languages/Prototype.pm	2009-07-01 15:30:00 UTC (rev 4275)
@@ -0,0 +1,92 @@
+###############################################################################
+#
+#   Class: NaturalDocs::Languages::Prototype
+#
+###############################################################################
+#
+#   A data class for storing parsed prototypes.
+#
+###############################################################################
+
+# 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;
+
+use NaturalDocs::Languages::Prototype::Parameter;
+
+
+package NaturalDocs::Languages::Prototype;
+
+use NaturalDocs::DefineMembers 'BEFORE_PARAMETERS', 'BeforeParameters()', 'SetBeforeParameters()',
+                                                 'AFTER_PARAMETERS', 'AfterParameters()', 'SetAfterParameters()',
+                                                 'PARAMETERS', 'Parameters()';
+# Dependency: New(), constant order, no parents.
+
+
+#
+#   Function: New
+#
+#   Creates and returns a new prototype object.
+#
+#   Parameters:
+#
+#       beforeParameters - The part of the prototype before the parameter list.
+#       afterParameters - The part of the prototype after the parameter list.
+#
+#   You cannot set the parameters from here.  Use <AddParameter()>.
+#
+sub New #(beforeParameters, afterParameters)
+    {
+    my ($package, @params) = @_;
+
+    # Dependency: Constant order, no parents.
+
+    my $object = [ @params ];
+    bless $object, $package;
+
+    return $object;
+    };
+
+
+#
+#   Functions: Members
+#
+#   BeforeParameters - Returns the part of the prototype before the parameter list.  If there is no parameter list, this will be the
+#                                only thing that returns content.
+#   SetBeforeParameters - Replaces the part of the prototype before the parameter list.
+#   AfterParameters - Returns the part of the prototype after the parameter list, if any.
+#   SetAfterParameters - Replaces the part of the prototype after the parameter list.
+#   Parameters - Returns the parameter list as an arrayref of <NaturalDocs::Languages::Prototype::Parameters>, or undef if none.
+#
+
+#
+#   Function: AddParameter
+#
+#   Adds a <NaturalDocs::Languages::Prototype::Parameter> to the list.
+#
+sub AddParameter #(parameter)
+    {
+    my ($self, $parameter) = @_;
+
+    if (!defined $self->[PARAMETERS])
+        {  $self->[PARAMETERS] = [ ];  };
+
+    push @{$self->[PARAMETERS]}, $parameter;
+    };
+
+
+#
+#   Function: OnlyBeforeParameters
+#
+#   Returns whether <BeforeParameters()> is the only thing set.
+#
+sub OnlyBeforeParameters
+    {
+    my $self = shift;
+    return (!defined $self->[PARAMETERS] && !defined $self->[AFTER_PARAMETERS]);
+    };
+
+
+1;

Added: trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Languages/Simple.pm
===================================================================
--- trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Languages/Simple.pm	                        (rev 0)
+++ trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Languages/Simple.pm	2009-07-01 15:30:00 UTC (rev 4275)
@@ -0,0 +1,503 @@
+###############################################################################
+#
+#   Class: NaturalDocs::Languages::Simple
+#
+###############################################################################
+#
+#   A class containing the characteristics of a particular programming language for basic support within Natural Docs.
+#   Also serves as a base class for languages that break from general conventions, such as not having parameter lists use
+#   parenthesis and commas.
+#
+###############################################################################
+
+# 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::Languages::Simple;
+
+use base 'NaturalDocs::Languages::Base';
+use base 'Exporter';
+
+our @EXPORT = ( 'ENDER_ACCEPT', 'ENDER_IGNORE', 'ENDER_ACCEPT_AND_CONTINUE', 'ENDER_REVERT_TO_ACCEPTED' );
+
+
+use NaturalDocs::DefineMembers 'LINE_COMMENT_SYMBOLS', 'LineCommentSymbols()', 'SetLineCommentSymbols() duparrayref',
+                                                 'BLOCK_COMMENT_SYMBOLS', 'BlockCommentSymbols()',
+                                                                                              'SetBlockCommentSymbols() duparrayref',
+                                                 'PROTOTYPE_ENDERS',
+                                                 'LINE_EXTENDER', 'LineExtender()', 'SetLineExtender()',
+                                                 'PACKAGE_SEPARATOR', 'PackageSeparator()',
+                                                 'PACKAGE_SEPARATOR_WAS_SET', 'PackageSeparatorWasSet()',
+                                                 'ENUM_VALUES', 'EnumValues()',
+                                                 'ENUM_VALUES_WAS_SET', 'EnumValuesWasSet()';
+
+#
+#   Function: New
+#
+#   Creates and returns a new object.
+#
+#   Parameters:
+#
+#       name - The name of the language.
+#
+sub New #(name)
+    {
+    my ($selfPackage, $name) = @_;
+
+    my $object = $selfPackage->SUPER::New($name);
+
+    $object->[ENUM_VALUES] = ::ENUM_GLOBAL();
+    $object->[PACKAGE_SEPARATOR] = '.';
+
+    return $object;
+    };
+
+
+#
+#   Functions: Members
+#
+#   LineCommentSymbols - Returns an arrayref of symbols that start a line comment, or undef if none.
+#   SetLineCommentSymbols - Replaces the arrayref of symbols that start a line comment.
+#   BlockCommentSymbols - Returns an arrayref of start/end symbol pairs that specify a block comment, or undef if none.  Pairs
+#                                        are specified with two consecutive array entries.
+#   SetBlockCommentSymbols - Replaces the arrayref of start/end symbol pairs that specify a block comment.  Pairs are
+#                                             specified with two consecutive array entries.
+#   LineExtender - Returns the symbol to ignore a line break in languages where line breaks are significant.
+#   SetLineExtender - Replaces the symbol to ignore a line break in languages where line breaks are significant.
+#   PackageSeparator - Returns the package separator symbol.
+#   PackageSeparatorWasSet - Returns whether the package separator symbol was ever changed from the default.
+#
+
+#
+#   Function: SetPackageSeparator
+#   Replaces the language's package separator string.
+#
+sub SetPackageSeparator #(separator)
+    {
+    my ($self, $separator) = @_;
+    $self->[PACKAGE_SEPARATOR] = $separator;
+    $self->[PACKAGE_SEPARATOR_WAS_SET] = 1;
+    };
+
+
+#
+#   Functions: Members
+#
+#   EnumValues - Returns the <EnumValuesType> that describes how the language handles enums.
+#   EnumValuesWasSet - Returns whether <EnumValues> was ever changed from the default.
+
+
+#
+#   Function: SetEnumValues
+#   Replaces the <EnumValuesType> that describes how the language handles enums.
+#
+sub SetEnumValues #(EnumValuesType newBehavior)
+    {
+    my ($self, $behavior) = @_;
+    $self->[ENUM_VALUES] = $behavior;
+    $self->[ENUM_VALUES_WAS_SET] = 1;
+    };
+
+
+#
+#   Function: PrototypeEndersFor
+#
+#   Returns an arrayref of prototype ender symbols for the passed <TopicType>, or undef if none.
+#
+sub PrototypeEndersFor #(type)
+    {
+    my ($self, $type) = @_;
+
+    if (defined $self->[PROTOTYPE_ENDERS])
+        {  return $self->[PROTOTYPE_ENDERS]->{$type};  }
+    else
+        {  return undef;  };
+    };
+
+
+#
+#   Function: SetPrototypeEndersFor
+#
+#   Replaces the arrayref of prototype ender symbols for the passed <TopicType>.
+#
+sub SetPrototypeEndersFor #(type, enders)
+    {
+    my ($self, $type, $enders) = @_;
+
+    if (!defined $self->[PROTOTYPE_ENDERS])
+        {  $self->[PROTOTYPE_ENDERS] = { };  };
+
+    if (!defined $enders)
+        {  delete $self->[PROTOTYPE_ENDERS]->{$type};  }
+    else
+        {
+        $self->[PROTOTYPE_ENDERS]->{$type} = [ @$enders ];
+        };
+    };
+
+
+
+
+###############################################################################
+# Group: Parsing Functions
+
+
+#
+#   Function: ParseFile
+#
+#   Parses the passed source file, sending comments acceptable for documentation to <NaturalDocs::Parser->OnComment()>
+#   and all other sections to <OnCode()>.
+#
+#   Parameters:
+#
+#       sourceFile - The <FileName> of the source file to parse.
+#       topicList - A reference to the list of <NaturalDocs::Parser::ParsedTopics> being built by the file.
+#
+#   Returns:
+#
+#       Since this class cannot automatically document the code or generate a scope record, it always returns ( undef, undef ).
+#
+sub ParseFile #(sourceFile, topicsList)
+    {
+    my ($self, $sourceFile, $topicsList) = @_;
+
+    open(SOURCEFILEHANDLE, '<' . $sourceFile)
+        or die "Couldn't open input file " . $sourceFile . "\n";
+
+    my @commentLines;
+    my @codeLines;
+    my $lastCommentTopicCount = 0;
+
+    if ($self->Name() eq 'Text File')
+        {
+        my $line = <SOURCEFILEHANDLE>;
+
+        # On the very first line, remove a Unicode BOM if present.  Information on it available at:
+        # http://www.unicode.org/faq/utf_bom.html#BOM
+        $line =~ s/^\xEF\xBB\xBF//;
+
+        while ($line)
+            {
+            ::XChomp(\$line);
+            push @commentLines, $line;
+            $line = <SOURCEFILEHANDLE>;
+            };
+
+        NaturalDocs::Parser->OnComment(\@commentLines, 1);
+        }
+
+    else
+        {
+        my $line = <SOURCEFILEHANDLE>;
+        my $lineNumber = 1;
+
+        # On the very first line, remove a Unicode BOM if present.  Information on it available at:
+        # http://www.unicode.org/faq/utf_bom.html#BOM
+        $line =~ s/^\xEF\xBB\xBF//;
+
+        while (defined $line)
+            {
+            ::XChomp(\$line);
+            my $originalLine = $line;
+
+
+            # Retrieve single line comments.  This leaves $line at the next line.
+
+            if ($self->StripOpeningSymbols(\$line, $self->LineCommentSymbols()))
+                {
+                do
+                    {
+                    push @commentLines, $line;
+                    $line = <SOURCEFILEHANDLE>;
+
+                    if (!defined $line)
+                        {  goto EndDo;  };
+
+                    ::XChomp(\$line);
+                    }
+                while ($self->StripOpeningSymbols(\$line, $self->LineCommentSymbols()));
+
+                EndDo:  # I hate Perl sometimes.
+                }
+
+
+            # Retrieve multiline comments.  This leaves $line at the next line.
+
+            elsif (my $closingSymbol = $self->StripOpeningBlockSymbols(\$line, $self->BlockCommentSymbols()))
+                {
+                # Note that it is possible for a multiline comment to start correctly but not end so.  We want those comments to stay in
+                # the code.  For example, look at this prototype with this splint annotation:
+                #
+                # int get_array(integer_t id,
+                #                    /*@out@*/ array_t array);
+                #
+                # The annotation starts correctly but doesn't end so because it is followed by code on the same line.
+
+                my $lineRemainder;
+
+                for (;;)
+                    {
+                    $lineRemainder = $self->StripClosingSymbol(\$line, $closingSymbol);
+
+                    push @commentLines, $line;
+
+                    #  If we found an end comment symbol...
+                    if (defined $lineRemainder)
+                        {  last;  };
+
+                    $line = <SOURCEFILEHANDLE>;
+
+                    if (!defined $line)
+                        {  last;  };
+
+                    ::XChomp(\$line);
+                    };
+
+                if ($lineRemainder !~ /^[ \t]*$/)
+                    {
+                    # If there was something past the closing symbol this wasn't an acceptable comment, so move the lines to code.
+                    push @codeLines, @commentLines;
+                    @commentLines = ( );
+                    };
+
+                $line = <SOURCEFILEHANDLE>;
+                }
+
+
+            # Otherwise just add it to the code.
+
+            else
+                {
+                push @codeLines, $line;
+                $line = <SOURCEFILEHANDLE>;
+                };
+
+
+            # If there were comments, send them to Parser->OnComment().
+
+            if (scalar @commentLines)
+                {
+                # First process any code lines before the comment.
+                if (scalar @codeLines)
+                    {
+                    $self->OnCode(\@codeLines, $lineNumber, $topicsList, $lastCommentTopicCount);
+                    $lineNumber += scalar @codeLines;
+                    @codeLines = ( );
+                    };
+
+                $lastCommentTopicCount = NaturalDocs::Parser->OnComment(\@commentLines, $lineNumber);
+                $lineNumber += scalar @commentLines;
+                @commentLines = ( );
+                };
+
+            };  # while (defined $line)
+
+
+        # Clean up any remaining code.
+        if (scalar @codeLines)
+            {
+            $self->OnCode(\@codeLines, $lineNumber, $topicsList, $lastCommentTopicCount);
+            @codeLines = ( );
+            };
+
+        };
+
+    close(SOURCEFILEHANDLE);
+
+    return ( undef, undef );
+    };
+
+
+#
+#   Function: OnCode
+#
+#   Called whenever a section of code is encountered by the parser.  Is used to find the prototype of the last topic created.
+#
+#   Parameters:
+#
+#       codeLines - The source code as an arrayref of lines.
+#       codeLineNumber - The line number of the first line of code.
+#       topicList - A reference to the list of <NaturalDocs::Parser::ParsedTopics> being built by the file.
+#       lastCommentTopicCount - The number of Natural Docs topics that were created by the last comment.
+#
+sub OnCode #(codeLines, codeLineNumber, topicList, lastCommentTopicCount)
+    {
+    my ($self, $codeLines, $codeLineNumber, $topicList, $lastCommentTopicCount) = @_;
+
+    if ($lastCommentTopicCount && defined $self->PrototypeEndersFor($topicList->[-1]->Type()))
+        {
+        my $lineIndex = 0;
+        my $prototype;
+
+        # Skip all blank lines before a prototype.
+        while ($lineIndex < scalar @$codeLines && $codeLines->[$lineIndex] =~ /^[ \t]*$/)
+            {  $lineIndex++;  };
+
+        my @tokens;
+        my $tokenIndex = 0;
+
+        my @brackets;
+        my $enders = $self->PrototypeEndersFor($topicList->[-1]->Type());
+
+        # Add prototype lines until we reach the end of the prototype or the end of the code lines.
+        while ($lineIndex < scalar @$codeLines)
+            {
+            my $line = $self->RemoveLineExtender($codeLines->[$lineIndex] . "\n");
+
+            push @tokens, $line =~ /([^\(\)\[\]\{\}\<\>]+|.)/g;
+
+            while ($tokenIndex < scalar @tokens)
+                {
+                # If we're not inside brackets, check for ender symbols.
+                if (!scalar @brackets)
+                    {
+                    my $startingIndex = 0;
+                    my $testPrototype;
+
+                    for (;;)
+                        {
+                        my ($enderIndex, $ender) = ::FindFirstSymbol($tokens[$tokenIndex], $enders, $startingIndex);
+
+                        if ($enderIndex == -1)
+                            {  last;  }
+                        else
+                            {
+                            # We do this here so we don't duplicate prototype for every single token.  Just the first time an ender symbol
+                            # is found in one.
+                            if (!defined $testPrototype)
+                                {  $testPrototype = $prototype;  };
+
+                            $testPrototype .= substr($tokens[$tokenIndex], $startingIndex, $enderIndex - $startingIndex);
+
+                            my $enderResult;
+
+                            # If the ender is all text and the character preceding or following it is as well, ignore it.
+                            if ($ender =~ /^[a-z0-9]+$/i &&
+                                ( ($enderIndex > 0 && substr($tokens[$tokenIndex], $enderIndex - 1, 1) =~ /^[a-z0-9_]$/i) ||
+                                   substr($tokens[$tokenIndex], $enderIndex + length($ender), 1) =~ /^[a-z0-9_]$/i ) )
+                                {  $enderResult = ENDER_IGNORE();  }
+                            else
+                                {  $enderResult = $self->OnPrototypeEnd($topicList->[-1]->Type(), \$testPrototype, $ender);  }
+
+                            if ($enderResult == ENDER_IGNORE())
+                                {
+                                $testPrototype .= $ender;
+                                $startingIndex = $enderIndex + length($ender);
+                                }
+                            elsif ($enderResult == ENDER_REVERT_TO_ACCEPTED())
+                                {
+                                return;
+                                }
+                            else # ENDER_ACCEPT || ENDER_ACCEPT_AND_CONTINUE
+                                {
+                                my $titleInPrototype = $topicList->[-1]->Title();
+
+                                # Strip parenthesis so Function(2) and Function(int, int) will still match Function(anything).
+                                $titleInPrototype =~ s/[\t ]*\([^\(]*$//;
+
+                                if (index($testPrototype, $titleInPrototype) != -1)
+                                    {
+                                    $topicList->[-1]->SetPrototype( $self->NormalizePrototype($testPrototype) );
+                                    };
+
+                                if ($enderResult == ENDER_ACCEPT())
+                                    {  return;  }
+                                else # ENDER_ACCEPT_AND_CONTINUE
+                                    {
+                                    $testPrototype .= $ender;
+                                    $startingIndex = $enderIndex + length($ender);
+                                    };
+                                };
+                            };
+                        };
+                    }
+
+                # If we are inside brackets, check for closing symbols.
+                elsif ( ($tokens[$tokenIndex] eq ')' && $brackets[-1] eq '(') ||
+                         ($tokens[$tokenIndex] eq ']' && $brackets[-1] eq '[') ||
+                         ($tokens[$tokenIndex] eq '}' && $brackets[-1] eq '{') ||
+                         ($tokens[$tokenIndex] eq '>' && $brackets[-1] eq '<') )
+                    {
+                    pop @brackets;
+                    };
+
+                # Check for opening brackets.
+                if ($tokens[$tokenIndex] =~ /^[\(\[\{\<]$/)
+                    {
+                    push @brackets, $tokens[$tokenIndex];
+                    };
+
+                $prototype .= $tokens[$tokenIndex];
+                $tokenIndex++;
+                };
+
+            $lineIndex++;
+            };
+
+        # If we got out of that while loop by running out of lines, there was no prototype.
+        };
+    };
+
+
+use constant ENDER_ACCEPT => 1;
+use constant ENDER_IGNORE => 2;
+use constant ENDER_ACCEPT_AND_CONTINUE => 3;
+use constant ENDER_REVERT_TO_ACCEPTED => 4;
+
+#
+#   Function: OnPrototypeEnd
+#
+#   Called whenever the end of a prototype is found so that there's a chance for derived classes to mark false positives.
+#
+#   Parameters:
+#
+#       type - The <TopicType> of the prototype.
+#       prototypeRef - A reference to the prototype so far, minus the ender in dispute.
+#       ender - The ender symbol.
+#
+#   Returns:
+#
+#       ENDER_ACCEPT - The ender is accepted and the prototype is finished.
+#       ENDER_IGNORE - The ender is rejected and parsing should continue.  Note that the prototype will be rejected as a whole
+#                                  if all enders are ignored before reaching the end of the code.
+#       ENDER_ACCEPT_AND_CONTINUE - The ender is accepted so the prototype may stand as is.  However, the prototype might
+#                                                          also continue on so continue parsing.  If there is no accepted ender between here and
+#                                                          the end of the code this version will be accepted instead.
+#       ENDER_REVERT_TO_ACCEPTED - The expedition from ENDER_ACCEPT_AND_CONTINUE failed.  Use the last accepted
+#                                                        version and end parsing.
+#
+sub OnPrototypeEnd #(type, prototypeRef, ender)
+    {
+    return ENDER_ACCEPT();
+    };
+
+
+#
+#   Function: RemoveLineExtender
+#
+#   If the passed line has a line extender, returns it without the extender or the line break that follows.  If it doesn't, or there are
+#   no line extenders defined, returns the passed line unchanged.
+#
+sub RemoveLineExtender #(line)
+    {
+    my ($self, $line) = @_;
+
+    if (defined $self->LineExtender())
+        {
+        my $lineExtenderIndex = rindex($line, $self->LineExtender());
+
+        if ($lineExtenderIndex != -1 &&
+            substr($line, $lineExtenderIndex + length($self->LineExtender())) =~ /^[ \t]*\n$/)
+            {
+            $line = substr($line, 0, $lineExtenderIndex) . ' ';
+            };
+        };
+
+    return $line;
+    };
+
+
+1;

Added: trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Languages/Tcl.pm
===================================================================
--- trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Languages/Tcl.pm	                        (rev 0)
+++ trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Languages/Tcl.pm	2009-07-01 15:30:00 UTC (rev 4275)
@@ -0,0 +1,219 @@
+###############################################################################
+#
+#   Class: NaturalDocs::Languages::Tcl
+#
+###############################################################################
+#
+#   A subclass to handle the language variations of Tcl.
+#
+###############################################################################
+
+# 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::Languages::Tcl;
+
+use base 'NaturalDocs::Languages::Simple';
+
+
+#
+#   bool: pastFirstBrace
+#
+#   Whether we've past the first brace in a function prototype or not.
+#
+my $pastFirstBrace;
+
+
+#
+#   Function: OnCode
+#
+#   This is just overridden to reset <pastFirstBrace>.
+#
+sub OnCode #(...)
+    {
+    my ($self, @params) = @_;
+
+    $pastFirstBrace = 0;
+
+    return $self->SUPER::OnCode(@params);
+    };
+
+
+#
+#   Function: OnPrototypeEnd
+#
+#   Tcl's function syntax is shown below.
+#
+#   > proc [name] { [params] } { [code] }
+#
+#   The opening brace is one of the prototype enders.  We need to allow the first opening brace because it contains the
+#   parameters.
+#
+#   Also, the parameters may have braces within them.  I've seen one that used { seconds 20 } as a parameter.
+#
+#   Parameters:
+#
+#       type - The <TopicType> of the prototype.
+#       prototypeRef - A reference to the prototype so far, minus the ender in dispute.
+#       ender - The ender symbol.
+#
+#   Returns:
+#
+#       ENDER_ACCEPT - The ender is accepted and the prototype is finished.
+#       ENDER_IGNORE - The ender is rejected and parsing should continue.  Note that the prototype will be rejected as a whole
+#                                  if all enders are ignored before reaching the end of the code.
+#       ENDER_ACCEPT_AND_CONTINUE - The ender is accepted so the prototype may stand as is.  However, the prototype might
+#                                                          also continue on so continue parsing.  If there is no accepted ender between here and
+#                                                          the end of the code this version will be accepted instead.
+#       ENDER_REVERT_TO_ACCEPTED - The expedition from ENDER_ACCEPT_AND_CONTINUE failed.  Use the last accepted
+#                                                        version and end parsing.
+#
+sub OnPrototypeEnd #(type, prototypeRef, ender)
+    {
+    my ($self, $type, $prototypeRef, $ender) = @_;
+
+    if ($type eq ::TOPIC_FUNCTION() && $ender eq '{' && !$pastFirstBrace)
+        {
+        $pastFirstBrace = 1;
+        return ::ENDER_IGNORE();
+        }
+    else
+        {  return ::ENDER_ACCEPT();  };
+    };
+
+
+#
+#   Function: ParsePrototype
+#
+#   Parses the prototype and returns it as a <NaturalDocs::Languages::Prototype> object.
+#
+#   Parameters:
+#
+#       type - The <TopicType>.
+#       prototype - The text prototype.
+#
+#   Returns:
+#
+#       A <NaturalDocs::Languages::Prototype> object.
+#
+sub ParsePrototype #(type, prototype)
+    {
+    my ($self, $type, $prototype) = @_;
+
+    if ($type ne ::TOPIC_FUNCTION())
+        {
+        my $object = NaturalDocs::Languages::Prototype->New($prototype);
+        return $object;
+        };
+
+
+    # Parse the parameters out of the prototype.
+
+    my @tokens = $prototype =~ /([^\{\}\ ]+|.)/g;
+
+    my $parameter;
+    my @parameterLines;
+
+    my $braceLevel = 0;
+
+    my ($beforeParameters, $afterParameters, $finishedParameters);
+
+    foreach my $token (@tokens)
+        {
+        if ($finishedParameters)
+            {  $afterParameters .= $token;  }
+
+        elsif ($token eq '{')
+            {
+            if ($braceLevel == 0)
+                {  $beforeParameters .= $token;  }
+
+            else # braceLevel > 0
+                {  $parameter .= $token;   };
+
+            $braceLevel++;
+            }
+
+        elsif ($token eq '}')
+            {
+            if ($braceLevel == 1)
+                {
+                if ($parameter && $parameter ne ' ')
+                    {  push @parameterLines, $parameter;  };
+
+                $finishedParameters = 1;
+                $afterParameters .= $token;
+
+                $braceLevel--;
+                }
+            elsif ($braceLevel > 1)
+                {
+                $parameter .= $token;
+                $braceLevel--;
+                };
+            }
+
+        elsif ($token eq ' ')
+            {
+            if ($braceLevel == 1)
+                {
+                if ($parameter)
+                    {  push @parameterLines, $parameter;  };
+
+                $parameter = undef;
+                }
+            elsif ($braceLevel > 1)
+                {
+                $parameter .= $token;
+                }
+            else
+                {
+                $beforeParameters .= $token;
+                };
+            }
+
+        else
+            {
+            if ($braceLevel > 0)
+                {  $parameter .= $token;  }
+            else
+                {  $beforeParameters .= $token;  };
+            };
+        };
+
+    foreach my $part (\$beforeParameters, \$afterParameters)
+        {
+        $$part =~ s/^ //;
+        $$part =~ s/ $//;
+        };
+
+    my $prototypeObject = NaturalDocs::Languages::Prototype->New($beforeParameters, $afterParameters);
+
+
+    # Parse the actual parameters.
+
+    foreach my $parameterLine (@parameterLines)
+        {
+        $prototypeObject->AddParameter( $self->ParseParameterLine($parameterLine) );
+        };
+
+    return $prototypeObject;
+    };
+
+
+#
+#   Function: ParseParameterLine
+#
+#   Parses a prototype parameter line and returns it as a <NaturalDocs::Languages::Prototype::Parameter> object.
+#
+sub ParseParameterLine #(line)
+    {
+    my ($self, $line) = @_;
+    return NaturalDocs::Languages::Prototype::Parameter->New(undef, undef, $line, undef, undef, undef);
+    };
+
+
+1;

Added: trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Languages.pm
===================================================================
--- trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Languages.pm	                        (rev 0)
+++ trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Languages.pm	2009-07-01 15:30:00 UTC (rev 4275)
@@ -0,0 +1,1475 @@
+###############################################################################
+#
+#   Package: NaturalDocs::Languages
+#
+###############################################################################
+#
+#   A package to manage all the programming languages Natural Docs supports.
+#
+#   Usage and Dependencies:
+#
+#       - Prior to use, <NaturalDocs::Settings> must be initialized and <Load()> must be called.
+#
+###############################################################################
+
+# This file is part of Natural Docs, which is Copyright (C) 2003-2008 Greg Valure
+# Natural Docs is licensed under the GPL
+
+use Text::Wrap();
+
+use NaturalDocs::Languages::Prototype;
+
+use NaturalDocs::Languages::Base;
+use NaturalDocs::Languages::Simple;
+use NaturalDocs::Languages::Advanced;
+
+use NaturalDocs::Languages::Perl;
+use NaturalDocs::Languages::CSharp;
+use NaturalDocs::Languages::ActionScript;
+
+use NaturalDocs::Languages::Ada;
+use NaturalDocs::Languages::PLSQL;
+use NaturalDocs::Languages::Pascal;
+use NaturalDocs::Languages::Tcl;
+
+use strict;
+use integer;
+
+package NaturalDocs::Languages;
+
+
+###############################################################################
+# Group: Variables
+
+
+#
+#   handle: FH_LANGUAGES
+#
+#   The file handle used for writing to <Languages.txt>.
+#
+
+
+#
+#   hash: languages
+#
+#   A hash of all the defined languages.  The keys are the all-lowercase language names, and the values are
+#   <NaturalDocs::Languages::Base>-derived objects.
+#
+my %languages;
+
+#
+#   hash: extensions
+#
+#   A hash of all the defined languages' extensions.  The keys are the all-lowercase extensions, and the values are the
+#   all-lowercase names of the languages that defined them.
+#
+my %extensions;
+
+#
+#   hash: shebangStrings
+#
+#   A hash of all the defined languages' strings to search for in the shebang (#!) line.  The keys are the all-lowercase strings, and
+#   the values are the all-lowercase names of the languages that defined them.
+#
+my %shebangStrings;
+
+#
+#   hash: shebangFiles
+#
+#   A hash of all the defined languages for files where it needs to be found via shebang strings.  The keys are the file names,
+#   and the values are language names, or undef if the file isn't supported.  These values should be filled in the first time
+#   each file is parsed for a shebang string so that it doesn't have to be done multiple times.
+#
+my %shebangFiles;
+
+#
+#   array: mainLanguageNames
+#
+#   An array of the language names that are defined in the main <Languages.txt>.
+#
+my @mainLanguageNames;
+
+
+
+###############################################################################
+# Group: Files
+
+
+#
+#   File: Languages.txt
+#
+#   The configuration file that defines or overrides the language definitions for Natural Docs.  One version sits in Natural Docs'
+#   configuration directory, and another can be in a project directory to add to or override them.
+#
+#   > # [comments]
+#
+#   Everything after a # symbol is ignored.  However, for this particular file, comments can only appear on their own lines.
+#   They cannot appear after content on the same line.
+#
+#   > Format: [version]
+#
+#   Specifies the file format version of the file.
+#
+#
+#   Sections:
+#
+#       > Ignore[d] Extension[s]: [extension] [extension] ...
+#
+#       Causes the listed file extensions to be ignored, even if they were previously defined to be part of a language.  The list is
+#       space-separated.  ex. "Ignore Extensions: cvs txt"
+#
+#
+#       > Language: [name]
+#
+#       Creates a new language.  Everything underneath applies to this language until the next one.  Names can use any
+#       characters.
+#
+#       The languages "Text File" and "Shebang Script" have special meanings.  Text files are considered all comment and don't
+#       have comment symbols.  Shebang scripts have their language determined by the shebang string and automatically
+#       include files with no extension in addition to the extensions defined.
+#
+#       If "Text File" doesn't define ignored prefixes, a package separator, or enum value behavior, those settings will be copied
+#       from the language with the most files in the source tree.
+#
+#
+#       > Alter Language: [name]
+#
+#       Alters an existing language.  Everything underneath it overrides the previous settings until the next one.  Note that if a
+#       property has an [Add/Replace] form and that property has already been defined, you have to specify whether you're adding
+#       to or replacing the defined list.
+#
+#
+#   Language Properties:
+#
+#       > Extension[s]: [extension] [extension] ...
+#       > [Add/Replace] Extension[s]: ...
+#
+#       Defines file extensions for the language's source files.  The list is space-separated.  ex. "Extensions: c cpp".  You can use
+#       extensions that were previously used by another language to redefine them.
+#
+#
+#       > Shebang String[s]: [string] [string] ...
+#       > [Add/Replace] Shebang String[s]: ...
+#
+#       Defines a list of strings that can appear in the shebang (#!) line to designate that it's part of this language.  They can
+#       appear anywhere in the line, so "php" will work for "#!/user/bin/php4".  You can use strings that were previously used by
+#       another language to redefine them.
+#
+#
+#       > Ignore[d] Prefix[es] in Index: [prefix] [prefix] ...
+#       > Ignore[d] [Topic Type] Prefix[es] in Index: [prefix] [prefix] ...
+#       > [Add/Replace] Ignore[d] Prefix[es] in Index: ...
+#       > [Add/Replace] Ignore[d] [Topic Type] Prefix[es] in Index: ...
+#
+#       Specifies prefixes that should be ignored when sorting symbols for an index.  Can be specified in general or for a specific
+#       <TopicType>.  The prefixes will still appear, the symbols will just be sorted as if they're not there.  For example, specifying
+#       "ADO_" for functions will mean that "ADO_DoSomething" will appear under D instead of A.
+#
+#
+#   Basic Language Support Properties:
+#
+#       These attributes are only available for languages with basic language support.
+#
+#
+#       > Line Comment[s]: [symbol] [symbol] ...
+#
+#       Defines a space-separated list of symbols that are used for line comments, if any.  ex. "Line Comment: //".
+#
+#
+#       > Block Comment[s]: [opening symbol] [closing symbol] [opening symbol] [closing symbol] ...
+#
+#       Defines a space-separated list of symbol pairs that are used for block comments, if any.  ex. "Block Comment: /* */".
+#
+#
+#       > Package Separator: [symbol]
+#
+#       Defines the default package separator symbol, such as . or ::.  This is for presentation only and will not affect how
+#       Natural Docs links are parsed.  The default is a dot.
+#
+#
+#       > [Topic Type] Prototype Ender[s]: [symbol] [symbol] ...
+#
+#       When defined, Natural Docs will attempt to collect prototypes from the code following the specified <TopicType>.  It grabs
+#       code until the first ender symbol or the next Natural Docs comment, and if it contains the topic name, it serves as its
+#       prototype.  Use \n to specify a line break.  ex. "Function Prototype Enders: { ;", "Variable Prototype Enders: = ;".
+#
+#
+#       > Line Extender: [symbol]
+#
+#       Defines the symbol that allows a prototype to span multiple lines if normally a line break would end it.
+#
+#
+#       > Enum Values: [global|under type|under parent]
+#
+#       Defines how enum values are referenced.  The default is global.
+#
+#       global - Values are always global, referenced as 'value'.
+#       under type - Values are under the enum type, referenced as 'package.enum.value'.
+#       under parent - Values are under the enum's parent, referenced as 'package.value'.
+#
+#
+#       > Perl Package: [perl package]
+#
+#       Specifies the Perl package used to fine-tune the language behavior in ways too complex to do in this file.
+#
+#
+#   Full Language Support Properties:
+#
+#       These attributes are only available for languages with full language support.
+#
+#
+#       > Full Language Support: [perl package]
+#
+#       Specifies the Perl package that has the parsing routines necessary for full language support.
+#
+#
+#   Revisions:
+#
+#       1.32:
+#
+#           - Package Separator is now a basic language support only property.
+#           - Added Enum Values setting.
+#
+#       1.3:
+#
+#           - The file was introduced.
+
+
+###############################################################################
+# Group: File Functions
+
+
+#
+#   Function: Load
+#
+#   Loads both the master and the project version of <Languages.txt>.
+#
+sub Load
+    {
+    my $self = shift;
+
+    # Hashrefs where the keys are all-lowercase extensions/shebang strings, and the values are arrayrefs of the languages
+    # that defined them, earliest first, all lowercase.
+    my %tempExtensions;
+    my %tempShebangStrings;
+
+    $self->LoadFile(1, \%tempExtensions, \%tempShebangStrings);  # Main
+
+    if (!exists $languages{'shebang script'})
+        {  NaturalDocs::ConfigFile->AddError('You must define "Shebang Script" in the main languages file.');  };
+    if (!exists $languages{'text file'})
+        {  NaturalDocs::ConfigFile->AddError('You must define "Text File" in the main languages file.');  };
+
+    my $errorCount = NaturalDocs::ConfigFile->ErrorCount();
+
+    if ($errorCount)
+        {
+        NaturalDocs::ConfigFile->PrintErrorsAndAnnotateFile();
+        NaturalDocs::Error->SoftDeath('There ' . ($errorCount == 1 ? 'is an error' : 'are ' . $errorCount . ' errors')
+                                                    . ' in ' . NaturalDocs::Project->MainConfigFile('Languages.txt'));
+        }
+
+
+    $self->LoadFile(0, \%tempExtensions, \%tempShebangStrings);  # User
+
+    $errorCount = NaturalDocs::ConfigFile->ErrorCount();
+
+    if ($errorCount)
+        {
+        NaturalDocs::ConfigFile->PrintErrorsAndAnnotateFile();
+        NaturalDocs::Error->SoftDeath('There ' . ($errorCount == 1 ? 'is an error' : 'are ' . $errorCount . ' errors')
+                                                    . ' in ' . NaturalDocs::Project->UserConfigFile('Languages.txt'));
+        };
+
+
+    # Convert the temp hashes into the real ones.
+
+    while (my ($extension, $languages) = each %tempExtensions)
+        {
+        $extensions{$extension} = $languages->[-1];
+        };
+    while (my ($shebangString, $languages) = each %tempShebangStrings)
+        {
+        $shebangStrings{$shebangString} = $languages->[-1];
+        };
+    };
+
+
+#
+#   Function: LoadFile
+#
+#   Loads a particular version of <Languages.txt>.
+#
+#   Parameters:
+#
+#       isMain - Whether the file is the main file or not.
+#       tempExtensions - A hashref where the keys are all-lowercase extensions, and the values are arrayrefs of the all-lowercase
+#                                 names of the languages that defined them, earliest first.  It will be changed by this function.
+#       tempShebangStrings - A hashref where the keys are all-lowercase shebang strings, and the values are arrayrefs of the
+#                                        all-lowercase names of the languages that defined them, earliest first.  It will be changed by this
+#                                        function.
+#
+sub LoadFile #(isMain, tempExtensions, tempShebangStrings)
+    {
+    my ($self, $isMain, $tempExtensions, $tempShebangStrings) = @_;
+
+    my ($file, $status);
+
+    if ($isMain)
+        {
+        $file = NaturalDocs::Project->MainConfigFile('Languages.txt');
+        $status = NaturalDocs::Project->MainConfigFileStatus('Languages.txt');
+        }
+    else
+        {
+        $file = NaturalDocs::Project->UserConfigFile('Languages.txt');
+        $status = NaturalDocs::Project->UserConfigFileStatus('Languages.txt');
+        };
+
+
+    my $version;
+
+    # An array of properties for the current language.  Each entry is the three consecutive values ( lineNumber, keyword, value ).
+    my @properties;
+
+    if ($version = NaturalDocs::ConfigFile->Open($file))
+        {
+        # The format hasn't changed significantly since the file was introduced.
+
+        if ($status == ::FILE_CHANGED())
+            {
+            NaturalDocs::Project->ReparseEverything();
+            NaturalDocs::SymbolTable->RebuildAllIndexes();  # Because the ignored prefixes could change.
+            };
+
+        my ($keyword, $value, $comment);
+
+        while (($keyword, $value, $comment) = NaturalDocs::ConfigFile->GetLine())
+            {
+            $value .= $comment;
+            $value =~ s/^ //;
+
+            # Process previous properties.
+            if (($keyword eq 'language' || $keyword eq 'alter language') && scalar @properties)
+                {
+                if ($isMain && $properties[1] eq 'language')
+                    {  push @mainLanguageNames, $properties[2];  };
+
+                $self->ProcessProperties(\@properties, $version, $tempExtensions, $tempShebangStrings);
+                @properties = ( );
+                };
+
+            if ($keyword =~ /^ignored? extensions?$/)
+                {
+                $value =~ tr/.*//d;
+                my @extensions = split(/ /, lc($value));
+
+                foreach my $extension (@extensions)
+                    {  delete $tempExtensions->{$extension};  };
+                }
+            else
+                {
+                push @properties, NaturalDocs::ConfigFile->LineNumber(), $keyword, $value;
+                };
+            };
+
+        if (scalar @properties)
+            {
+            if ($isMain && $properties[1] eq 'language')
+                {  push @mainLanguageNames, $properties[2];  };
+
+            $self->ProcessProperties(\@properties, $version, $tempExtensions, $tempShebangStrings);
+            };
+        }
+
+    else # couldn't open file
+        {
+        if ($isMain)
+            {  die "Couldn't open languages file " . $file . "\n";  };
+        };
+    };
+
+
+#
+#   Function: ProcessProperties
+#
+#   Processes an array of language properties from <Languages.txt>.
+#
+#   Parameters:
+#
+#       properties - An arrayref of properties where each entry is the three consecutive values ( lineNumber, keyword, value ).
+#                         It must start with the Language or Alter Language property.
+#       version - The <VersionInt> of the file.
+#       tempExtensions - A hashref where the keys are all-lowercase extensions, and the values are arrayrefs of the all-lowercase
+#                                 names of the languages that defined them, earliest first.  It will be changed by this function.
+#       tempShebangStrings - A hashref where the keys are all-lowercase shebang strings, and the values are arrayrefs of the
+#                                        all-lowercase names of the languages that defined them, earliest first.  It will be changed by this
+#                                        function.
+#
+sub ProcessProperties #(properties, version, tempExtensions, tempShebangStrings)
+    {
+    my ($self, $properties, $version, $tempExtensions, $tempShebangStrings) = @_;
+
+
+    # First validate the name and check whether the language has full support.
+
+    my $language;
+    my $fullLanguageSupport;
+    my ($lineNumber, $languageKeyword, $languageName) = @$properties[0..2];
+    my $lcLanguageName = lc($languageName);
+    my ($keyword, $value);
+
+    if ($languageKeyword eq 'alter language')
+        {
+        $language = $languages{$lcLanguageName};
+
+        if (!defined $language)
+            {
+            NaturalDocs::ConfigFile->AddError('The language ' . $languageName . ' is not defined.', $lineNumber);
+            return;
+            }
+        else
+            {
+            $fullLanguageSupport = (!$language->isa('NaturalDocs::Languages::Simple'));
+            };
+        }
+
+    elsif ($languageKeyword eq 'language')
+        {
+        if (exists $languages{$lcLanguageName})
+            {
+            NaturalDocs::ConfigFile->AddError('The language ' . $value . ' is already defined.  Use "Alter Language" if you want '
+                                                             . 'to override its settings.', $lineNumber);
+            return;
+            };
+
+        # Case is important with these two.
+        if ($lcLanguageName eq 'shebang script')
+            {  $languageName = 'Shebang Script';  }
+        elsif ($lcLanguageName eq 'text file')
+            {  $languageName = 'Text File';  };
+
+
+        # Go through the properties looking for whether the language has basic or full support and which package to use to create
+        # it.
+
+        for (my $i = 3; $i < scalar @$properties; $i += 3)
+            {
+            ($lineNumber, $keyword, $value) = @$properties[$i..$i+2];
+
+            if ($keyword eq 'full language support')
+                {
+                $fullLanguageSupport = 1;
+
+                eval
+                    {
+                    $language = $value->New($languageName);
+                    };
+                if ($::EVAL_ERROR)
+                    {
+                    NaturalDocs::ConfigFile->AddError('Could not create ' . $value . ' object.', $lineNumber);
+                    return;
+                    };
+
+                last;
+                }
+
+            elsif ($keyword eq 'perl package')
+                {
+                eval
+                    {
+                    $language = $value->New($languageName);
+                    };
+                if ($::EVAL_ERROR)
+                    {
+                    NaturalDocs::ConfigFile->AddError('Could not create ' . $value . ' object.', $lineNumber);
+                    return;
+                    };
+                };
+            };
+
+        # If $language was not created by now, it's a generic basic support language.
+        if (!defined $language)
+            {  $language = NaturalDocs::Languages::Simple->New($languageName);  };
+
+        $languages{$lcLanguageName} = $language;
+        }
+
+    else # not language or alter language
+        {
+        NaturalDocs::ConfigFile->AddError('You must start this line with "Language", "Alter Language", or "Ignore Extensions".',
+                                                           $lineNumber);
+        return;
+        };
+
+
+    # Decode the properties.
+
+    for (my $i = 3; $i < scalar @$properties; $i += 3)
+        {
+        ($lineNumber, $keyword, $value) = @$properties[$i..$i+2];
+
+        if ($keyword =~ /^(?:(add|replace) )?extensions?$/)
+            {
+            my $command = $1;
+
+
+            # Remove old extensions.
+
+            if (defined $language->Extensions() && $command eq 'replace')
+                {
+                foreach my $extension (@{$language->Extensions()})
+                    {
+                    if (exists $tempExtensions->{$extension})
+                        {
+                        my $languages = $tempExtensions->{$extension};
+                        my $i = 0;
+
+                        while ($i < scalar @$languages)
+                            {
+                            if ($languages->[$i] eq $lcLanguageName)
+                                {  splice(@$languages, $i, 1);  }
+                            else
+                                {  $i++;  };
+                            };
+
+                        if (!scalar @$languages)
+                            {  delete $tempExtensions->{$extension};  };
+                        };
+                    };
+                };
+
+
+            # Add new extensions.
+
+            # Ignore stars and dots so people could use .ext or *.ext.
+            $value =~ s/\*\.|\.//g;
+
+            my @extensions = split(/ /, lc($value));
+
+            foreach my $extension (@extensions)
+                {
+                if (!exists $tempExtensions->{$extension})
+                    {  $tempExtensions->{$extension} = [ ];  };
+
+                push @{$tempExtensions->{$extension}}, $lcLanguageName;
+                };
+
+
+            # Set the extensions for the language object.
+
+            if (defined $language->Extensions())
+                {
+                if ($command eq 'add')
+                    {  push @extensions, @{$language->Extensions()};  }
+                elsif (!$command)
+                    {
+                    NaturalDocs::ConfigFile->AddError('You need to specify whether you are adding to or replacing the list of extensions.',
+                                                                       $lineNumber);
+                    };
+                };
+
+            $language->SetExtensions(\@extensions);
+            }
+
+        elsif ($keyword =~ /^(?:(add|replace) )?shebang strings?$/)
+            {
+            my $command = $1;
+
+
+            # Remove old strings.
+
+            if (defined $language->ShebangStrings() && $command eq 'replace')
+                {
+                foreach my $shebangString (@{$language->ShebangStrings()})
+                    {
+                    if (exists $tempShebangStrings->{$shebangString})
+                        {
+                        my $languages = $tempShebangStrings->{$shebangString};
+                        my $i = 0;
+
+                        while ($i < scalar @$languages)
+                            {
+                            if ($languages->[$i] eq $lcLanguageName)
+                                {  splice(@$languages, $i, 1);  }
+                            else
+                                {  $i++;  };
+                            };
+
+                        if (!scalar @$languages)
+                            {  delete $tempShebangStrings->{$shebangString};  };
+                        };
+                    };
+                };
+
+
+            # Add new strings.
+
+            my @shebangStrings = split(/ /, lc($value));
+
+            foreach my $shebangString (@shebangStrings)
+                {
+                if (!exists $tempShebangStrings->{$shebangString})
+                    {  $tempShebangStrings->{$shebangString} = [ ];  };
+
+                push @{$tempShebangStrings->{$shebangString}}, $lcLanguageName;
+                };
+
+
+            # Set the strings for the language object.
+
+            if (defined $language->ShebangStrings())
+                {
+                if ($command eq 'add')
+                    {  push @shebangStrings, @{$language->ShebangStrings()};  }
+                elsif (!$command)
+                    {
+                    NaturalDocs::ConfigFile->AddError('You need to specify whether you are adding to or replacing the list of shebang '
+                                                                     . 'strings.', $lineNumber);
+                    };
+                };
+
+            $language->SetShebangStrings(\@shebangStrings);
+            }
+
+        elsif ($keyword eq 'package separator')
+            {
+            if ($fullLanguageSupport)
+                {
+                # Prior to 1.32, package separator was used with full language support too.  Accept it without complaining, even though
+                # we ignore it.
+                if ($version >= NaturalDocs::Version->FromString('1.32'))
+                    {
+                    NaturalDocs::ConfigFile->AddError('You cannot define this property when using full language support.', $lineNumber);
+                    };
+                }
+            else
+                {  $language->SetPackageSeparator($value);  };
+            }
+
+        elsif ($keyword =~ /^(?:(add|replace) )?ignored? (?:(.+) )?prefix(?:es)? in index$/)
+            {
+            my ($command, $topicName) = ($1, $2);
+            my $topicType;
+
+            if ($topicName)
+                {
+                if (!( ($topicType, undef) = NaturalDocs::Topics->NameInfo($topicName) ))
+                    {
+                    NaturalDocs::ConfigFile->AddError($topicName . ' is not a defined topic type.', $lineNumber);
+                    };
+                }
+            else
+                {  $topicType = ::TOPIC_GENERAL();  };
+
+            if ($topicType)
+                {
+                my @prefixes;
+
+                if (defined $language->IgnoredPrefixesFor($topicType))
+                    {
+                    if ($command eq 'add')
+                        {  @prefixes = @{$language->IgnoredPrefixesFor($topicType)};  }
+                    elsif (!$command)
+                        {
+                        NaturalDocs::ConfigFile->AddError('You need to specify whether you are adding to or replacing the list of '
+                                                                         . 'ignored prefixes.', $lineNumber);
+                        };
+                    };
+
+                push @prefixes, split(/ /, $value);
+                $language->SetIgnoredPrefixesFor($topicType, \@prefixes);
+                };
+            }
+
+        elsif ($keyword eq 'full language support' || $keyword eq 'perl package')
+            {
+            if ($languageKeyword eq 'alter language')
+                {
+                NaturalDocs::ConfigFile->AddError('You cannot use ' . $keyword . ' with Alter Language.', $lineNumber);
+                };
+            # else ignore it.
+            }
+
+        elsif ($keyword =~ /^line comments?$/)
+            {
+            if ($fullLanguageSupport)
+                {
+                NaturalDocs::ConfigFile->AddError('You cannot define this property when using full language support.', $lineNumber);
+                }
+            else
+                {
+                my @symbols = split(/ /, $value);
+                $language->SetLineCommentSymbols(\@symbols);
+                };
+            }
+
+        elsif ($keyword =~ /^block comments?$/)
+            {
+            if ($fullLanguageSupport)
+                {
+                NaturalDocs::ConfigFile->AddError('You cannot define this property when using full language support.', $lineNumber);
+                }
+            else
+                {
+                my @symbols = split(/ /, $value);
+
+                if ((scalar @symbols) % 2 == 0)
+                    {  $language->SetBlockCommentSymbols(\@symbols);  }
+                else
+                    {  NaturalDocs::ConfigFile->AddError('Block comment symbols must appear in pairs.', $lineNumber);  };
+                };
+            }
+
+        elsif ($keyword =~ /^(?:(.+) )?prototype enders?$/)
+            {
+            if ($fullLanguageSupport)
+                {
+                NaturalDocs::ConfigFile->AddError('You cannot define this property when using full language support.', $lineNumber);
+                }
+            else
+                {
+                my $topicName = $1;
+                my $topicType;
+
+                if ($topicName)
+                    {
+                    if (!( ($topicType, undef) = NaturalDocs::Topics->NameInfo($topicName) ))
+                        {
+                        NaturalDocs::ConfigFile->AddError($topicName . ' is not a defined topic type.', $lineNumber);
+                        };
+                    }
+                else
+                    {  $topicType = ::TOPIC_GENERAL();  };
+
+                if ($topicType)
+                    {
+                    $value =~ s/\\n/\n/g;
+                    my @symbols = split(/ /, $value);
+                    $language->SetPrototypeEndersFor($topicType, \@symbols);
+                    };
+                };
+            }
+
+        elsif ($keyword eq 'line extender')
+            {
+            if ($fullLanguageSupport)
+                {
+                NaturalDocs::ConfigFile->AddError('You cannot define this property when using full language support.', $lineNumber);
+                }
+            else
+                {
+                $language->SetLineExtender($value);
+                };
+            }
+
+        elsif ($keyword eq 'enum values')
+            {
+            if ($fullLanguageSupport)
+                {
+                NaturalDocs::ConfigFile->AddError('You cannot define this property when using full language support.', $lineNumber);
+                }
+            else
+                {
+                $value = lc($value);
+                my $constant;
+
+                if ($value eq 'global')
+                    {  $constant = ::ENUM_GLOBAL();  }
+                elsif ($value eq 'under type')
+                    {  $constant = ::ENUM_UNDER_TYPE();  }
+                elsif ($value eq 'under parent')
+                    {  $constant = ::ENUM_UNDER_PARENT();  };
+
+                if (defined $constant)
+                    {  $language->SetEnumValues($constant);  }
+                else
+                    {
+                    NaturalDocs::ConfigFile->AddError('Enum Values must be "Global", "Under Type", or "Under Parent".', $lineNumber);
+                    };
+                };
+            }
+
+        else
+            {
+            NaturalDocs::ConfigFile->AddError($keyword . ' is not a valid keyword.', $lineNumber);
+            };
+        };
+    };
+
+
+#
+#   Function: Save
+#
+#   Saves the main and user versions of <Languages.txt>.
+#
+sub Save
+    {
+    my $self = shift;
+
+    $self->SaveFile(1); # Main
+    $self->SaveFile(0); # User
+    };
+
+
+#
+#   Function: SaveFile
+#
+#   Saves a particular version of <Topics.txt>.
+#
+#   Parameters:
+#
+#       isMain - Whether the file is the main file or not.
+#
+sub SaveFile #(isMain)
+    {
+    my ($self, $isMain) = @_;
+
+    my $file;
+
+    if ($isMain)
+        {
+        if (NaturalDocs::Project->MainConfigFileStatus('Languages.txt') == ::FILE_SAME())
+            {  return;  };
+        $file = NaturalDocs::Project->MainConfigFile('Languages.txt');
+        }
+    else
+        {
+        # Have to check the main too because this file lists the languages defined there.
+        if (NaturalDocs::Project->UserConfigFileStatus('Languages.txt') == ::FILE_SAME() &&
+            NaturalDocs::Project->MainConfigFileStatus('Languages.txt') == ::FILE_SAME())
+            {  return;  };
+        $file = NaturalDocs::Project->UserConfigFile('Languages.txt');
+        };
+
+
+    # Array of segments, with each being groups of three consecutive entries.  The first is the keyword ('language' or
+    # 'alter language'), the second is the value, and the third is a hashref of all the properties.
+    # - For properties that can accept a topic type, the property values are hashrefs mapping topic types to the values.
+    # - For properties that can accept 'add' or 'replace', there is an additional property ending in 'command' that stores it.
+    # - For properties that can accept both, the 'command' thing is applied to the topic types rather than the properties.
+    my @segments;
+
+    my @ignoredExtensions;
+
+    my $currentProperties;
+    my $version;
+
+    if ($version = NaturalDocs::ConfigFile->Open($file))
+        {
+        # We can assume the file is valid.
+
+        while (my ($keyword, $value, $comment) = NaturalDocs::ConfigFile->GetLine())
+            {
+            $value .= $comment;
+            $value =~ s/^ //;
+
+            if ($keyword eq 'language')
+                {
+                $currentProperties = { };
+
+                # Case is important with these two.
+                if (lc($value) eq 'shebang script')
+                    {  $value = 'Shebang Script';  }
+                elsif (lc($value) eq 'text file')
+                    {  $value = 'Text File';  };
+
+                push @segments, 'language', $value, $currentProperties;
+                }
+
+            elsif ($keyword eq 'alter language')
+                {
+                $currentProperties = { };
+                push @segments, 'alter language', $languages{lc($value)}->Name(), $currentProperties;
+                }
+
+            elsif ($keyword =~ /^ignored? extensions?$/)
+                {
+                $value =~ tr/*.//d;
+                push @ignoredExtensions, split(/ /, $value);
+                }
+
+            elsif ($keyword eq 'package separator' || $keyword eq 'full language support' || $keyword eq 'perl package' ||
+                    $keyword eq 'line extender' || $keyword eq 'enum values')
+                {
+                $currentProperties->{$keyword} = $value;
+                }
+
+            elsif ($keyword =~ /^line comments?$/)
+                {
+                $currentProperties->{'line comments'} = $value;
+                }
+            elsif ($keyword =~ /^block comments?$/)
+                {
+                $currentProperties->{'block comments'} = $value;
+                }
+
+            elsif ($keyword =~ /^(?:(add|replace) )?extensions?$/)
+                {
+                my $command = $1;
+
+                if ($command eq 'add' && exists $currentProperties->{'extensions'})
+                    {  $currentProperties->{'extensions'} .= ' ' . $value;  }
+                else
+                    {
+                    $currentProperties->{'extensions'} = $value;
+                    $currentProperties->{'extensions command'} = $command;
+                    };
+                }
+
+            elsif ($keyword =~ /^(?:(add|replace) )?shebang strings?$/)
+                {
+                my $command = $1;
+
+                if ($command eq 'add' && exists $currentProperties->{'shebang strings'})
+                    {  $currentProperties->{'shebang strings'} .= ' ' . $value;  }
+                else
+                    {
+                    $currentProperties->{'shebang strings'} = $value;
+                    $currentProperties->{'shebang strings command'} = $command;
+                    };
+                }
+
+            elsif ($keyword =~ /^(?:(.+) )?prototype enders?$/)
+                {
+                my $topicName = $1;
+                my $topicType;
+
+                if ($topicName)
+                    {  ($topicType, undef) = NaturalDocs::Topics->NameInfo($topicName);  }
+                else
+                    {  $topicType = ::TOPIC_GENERAL();  };
+
+                my $currentTypeProperties = $currentProperties->{'prototype enders'};
+
+                if (!defined $currentTypeProperties)
+                    {
+                    $currentTypeProperties = { };
+                    $currentProperties->{'prototype enders'} = $currentTypeProperties;
+                    };
+
+                $currentTypeProperties->{$topicType} = $value;
+                }
+
+            elsif ($keyword =~ /^(?:(add|replace) )?ignored? (?:(.+) )?prefix(?:es)? in index$/)
+                {
+                my ($command, $topicName) = ($1, $2);
+                my $topicType;
+
+                if ($topicName)
+                    {  ($topicType, undef) = NaturalDocs::Topics->NameInfo($topicName);  }
+                else
+                    {  $topicType = ::TOPIC_GENERAL();  };
+
+                my $currentTypeProperties = $currentProperties->{'ignored prefixes in index'};
+
+                if (!defined $currentTypeProperties)
+                    {
+                    $currentTypeProperties = { };
+                    $currentProperties->{'ignored prefixes in index'} = $currentTypeProperties;
+                    };
+
+                if ($command eq 'add' && exists $currentTypeProperties->{$topicType})
+                    {  $currentTypeProperties->{$topicType} .= ' ' . $value;  }
+                else
+                    {
+                    $currentTypeProperties->{$topicType} = $value;
+                    $currentTypeProperties->{$topicType . ' command'} = $command;
+                    };
+                };
+            };
+
+        NaturalDocs::ConfigFile->Close();
+        };
+
+
+    if (!open(FH_LANGUAGES, '>' . $file))
+        {
+        # The main file may be on a shared volume or some other place the user doesn't have write access to.  Since this is only to
+        # reformat the file, we can ignore the failure.
+        if ($isMain)
+            {  return;  }
+        else
+            {  die "Couldn't save " . $file;  };
+        };
+
+    print FH_LANGUAGES 'Format: ' . NaturalDocs::Settings->TextAppVersion() . "\n\n";
+
+    # Remember the 80 character limit.
+
+    if ($isMain)
+        {
+        print FH_LANGUAGES
+        "# This is the main Natural Docs languages file.  If you change anything here,\n"
+        . "# it will apply to EVERY PROJECT you use Natural Docs on.  If you'd like to\n"
+        . "# change something for just one project, edit the Languages.txt in its project\n"
+        . "# directory instead.\n";
+        }
+    else
+        {
+        print FH_LANGUAGES
+        "# This is the Natural Docs languages file for this project.  If you change\n"
+        . "# anything here, it will apply to THIS PROJECT ONLY.  If you'd like to change\n"
+        . "# something for all your projects, edit the Languages.txt in Natural Docs'\n"
+        . "# Config directory instead.\n\n\n";
+
+        if (scalar @ignoredExtensions == 1)
+            {
+            print FH_LANGUAGES
+            'Ignore Extension: ' . $ignoredExtensions[0] . "\n";
+            }
+        elsif (scalar @ignoredExtensions)
+            {
+            print FH_LANGUAGES
+            'Ignore Extensions: ' . join(' ', @ignoredExtensions) . "\n";
+            }
+        else
+            {
+            print FH_LANGUAGES
+            "# You can prevent certain file extensions from being scanned like this:\n"
+            . "# Ignore Extensions: [extension] [extension] ...\n"
+            };
+        };
+
+    print FH_LANGUAGES
+    "\n\n"
+    . "#-------------------------------------------------------------------------------\n"
+    . "# SYNTAX:\n"
+    . "#\n"
+    . "# Unlike other Natural Docs configuration files, in this file all comments\n"
+    . "# MUST be alone on a line.  Some languages deal with the # character, so you\n"
+    . "# cannot put comments on the same line as content.\n"
+    . "#\n"
+    . "# Also, all lists are separated with spaces, not commas, again because some\n"
+    . "# languages may need to use them.\n"
+    . "#\n";
+
+    if ($isMain)
+        {
+        print FH_LANGUAGES
+        "# Language: [name]\n"
+        . "#    Defines a new language.  Its name can use any characters.\n"
+        . "#\n";
+        }
+    else
+        {
+        print FH_LANGUAGES
+        "# Language: [name]\n"
+        . "# Alter Language: [name]\n"
+        . "#    Defines a new language or alters an existing one.  Its name can use any\n"
+        . "#    characters.  If any of the properties below have an add/replace form, you\n"
+        . "#    must use that when using Alter Language.\n"
+        . "#\n";
+        };
+
+    print FH_LANGUAGES
+    "#    The language Shebang Script is special.  It's entry is only used for\n"
+    . "#    extensions, and files with those extensions have their shebang (#!) lines\n"
+    . "#    read to determine the real language of the file.  Extensionless files are\n"
+    . "#    always treated this way.\n"
+    . "#\n"
+    . "#    The language Text File is also special.  It's treated as one big comment\n"
+    . "#    so you can put Natural Docs content in them without special symbols.  Also,\n"
+    . "#    if you don't specify a package separator, ignored prefixes, or enum value\n"
+    . "#    behavior, it will copy those settings from the language that is used most\n"
+    . "#    in the source tree.\n"
+    . "#\n"
+    . "# Extensions: [extension] [extension] ...\n";
+
+    if ($isMain)
+        {
+        print FH_LANGUAGES
+        "#    Defines the file extensions of the language's source files.  You can use *\n"
+        . "#    to mean any undefined extension.\n"
+        . "#\n"
+        . "# Shebang Strings: [string] [string] ...\n"
+        . "#    Defines a list of strings that can appear in the shebang (#!) line to\n"
+        . "#    designate that it's part of the language.\n"
+        . "#\n";
+        }
+    else
+        {
+        print FH_LANGUAGES
+        "# [Add/Replace] Extensions: [extension] [extension] ...\n"
+        . "#    Defines the file extensions of the language's source files.  You can\n"
+        . "#    redefine extensions found in the main languages file.  You can use * to\n"
+        . "#    mean any undefined extension.\n"
+        . "#\n"
+        . "# Shebang Strings: [string] [string] ...\n"
+        . "# [Add/Replace] Shebang Strings: [string] [string] ...\n"
+        . "#    Defines a list of strings that can appear in the shebang (#!) line to\n"
+        . "#    designate that it's part of the language.  You can redefine strings found\n"
+        . "#    in the main languages file.\n"
+        . "#\n";
+        };
+
+    print FH_LANGUAGES
+    "# Ignore Prefixes in Index: [prefix] [prefix] ...\n"
+    . (!$isMain ? "# [Add/Replace] Ignored Prefixes in Index: [prefix] [prefix] ...\n#\n" : '')
+    . "# Ignore [Topic Type] Prefixes in Index: [prefix] [prefix] ...\n"
+    . (!$isMain ? "# [Add/Replace] Ignored [Topic Type] Prefixes in Index: [prefix] [prefix] ...\n" : '')
+    . "#    Specifies prefixes that should be ignored when sorting symbols in an\n"
+    . "#    index.  Can be specified in general or for a specific topic type.\n"
+    . "#\n"
+    . "#------------------------------------------------------------------------------\n"
+    . "# For basic language support only:\n"
+    . "#\n"
+    . "# Line Comments: [symbol] [symbol] ...\n"
+    . "#    Defines a space-separated list of symbols that are used for line comments,\n"
+    . "#    if any.\n"
+    . "#\n"
+    . "# Block Comments: [opening sym] [closing sym] [opening sym] [closing sym] ...\n"
+    . "#    Defines a space-separated list of symbol pairs that are used for block\n"
+    . "#    comments, if any.\n"
+    . "#\n"
+    . "# Package Separator: [symbol]\n"
+    . "#    Defines the default package separator symbol.  The default is a dot.\n"
+    . "#\n"
+    . "# [Topic Type] Prototype Enders: [symbol] [symbol] ...\n"
+    . "#    When defined, Natural Docs will attempt to get a prototype from the code\n"
+    . "#    immediately following the topic type.  It stops when it reaches one of\n"
+    . "#    these symbols.  Use \\n for line breaks.\n"
+    . "#\n"
+    . "# Line Extender: [symbol]\n"
+    . "#    Defines the symbol that allows a prototype to span multiple lines if\n"
+    . "#    normally a line break would end it.\n"
+    . "#\n"
+    . "# Enum Values: [global|under type|under parent]\n"
+    . "#    Defines how enum values are referenced.  The default is global.\n"
+    . "#    global       - Values are always global, referenced as 'value'.\n"
+    . "#    under type   - Values are under the enum type, referenced as\n"
+    . "#               'package.enum.value'.\n"
+    . "#    under parent - Values are under the enum's parent, referenced as\n"
+    . "#               'package.value'.\n"
+    . "#\n"
+    . "# Perl Package: [perl package]\n"
+    . "#    Specifies the Perl package used to fine-tune the language behavior in ways\n"
+    . "#    too complex to do in this file.\n"
+    . "#\n"
+    . "#------------------------------------------------------------------------------\n"
+    . "# For full language support only:\n"
+    . "#\n"
+    . "# Full Language Support: [perl package]\n"
+    . "#    Specifies the Perl package that has the parsing routines necessary for full\n"
+    . "#    language support.\n"
+    . "#\n"
+    . "#-------------------------------------------------------------------------------\n\n";
+
+    if ($isMain)
+        {
+        print FH_LANGUAGES
+        "# The following languages MUST be defined in this file:\n"
+        . "#\n"
+        . "#    Text File, Shebang Script\n";
+        }
+    else
+        {
+        print FH_LANGUAGES
+        "# The following languages are defined in the main file, if you'd like to alter\n"
+        . "# them:\n"
+        . "#\n"
+        . Text::Wrap::wrap('#    ', '#    ', join(', ', @mainLanguageNames)) . "\n";
+        };
+
+    print FH_LANGUAGES "\n"
+    . "# If you add a language that you think would be useful to other developers\n"
+    . "# and should be included in Natural Docs by default, please e-mail it to\n"
+    . "# languages [at] naturaldocs [dot] org.\n";
+
+    my @topicTypeOrder = ( ::TOPIC_GENERAL(), ::TOPIC_CLASS(), ::TOPIC_FUNCTION(), ::TOPIC_VARIABLE(),
+                                         ::TOPIC_PROPERTY(), ::TOPIC_TYPE(), ::TOPIC_CONSTANT() );
+
+    for (my $i = 0; $i < scalar @segments; $i += 3)
+        {
+        my ($keyword, $name, $properties) = @segments[$i..$i+2];
+
+        print FH_LANGUAGES "\n\n";
+
+        if ($keyword eq 'language')
+            {  print FH_LANGUAGES 'Language: ' . $name . "\n\n";  }
+        else
+            {  print FH_LANGUAGES 'Alter Language: ' . $name . "\n\n";  };
+
+        if (exists $properties->{'extensions'})
+            {
+            print FH_LANGUAGES '   ';
+
+            if ($properties->{'extensions command'})
+                {  print FH_LANGUAGES ucfirst($properties->{'extensions command'}) . ' ';  };
+
+            my @extensions = split(/ /, $properties->{'extensions'}, 2);
+
+            if (scalar @extensions == 1)
+                {  print FH_LANGUAGES 'Extension: ';  }
+            else
+                {  print FH_LANGUAGES 'Extensions: ';  };
+
+            print FH_LANGUAGES lc($properties->{'extensions'}) . "\n";
+            };
+
+        if (exists $properties->{'shebang strings'})
+            {
+            print FH_LANGUAGES '   ';
+
+            if ($properties->{'shebang strings command'})
+                {  print FH_LANGUAGES ucfirst($properties->{'shebang strings command'}) . ' ';  };
+
+            my @shebangStrings = split(/ /, $properties->{'shebang strings'}, 2);
+
+            if (scalar @shebangStrings == 1)
+                {  print FH_LANGUAGES 'Shebang String: ';  }
+            else
+                {  print FH_LANGUAGES 'Shebang Strings: ';  };
+
+            print FH_LANGUAGES lc($properties->{'shebang strings'}) . "\n";
+            };
+
+        if (exists $properties->{'ignored prefixes in index'})
+            {
+            my $topicTypePrefixes = $properties->{'ignored prefixes in index'};
+
+            my %usedTopicTypes;
+            my @topicTypes = ( @topicTypeOrder, keys %$topicTypePrefixes );
+
+            foreach my $topicType (@topicTypes)
+                {
+                if ($topicType !~ / command$/ &&
+                    exists $topicTypePrefixes->{$topicType} &&
+                    !exists $usedTopicTypes{$topicType})
+                    {
+                    print FH_LANGUAGES '   ';
+
+                    if ($topicTypePrefixes->{$topicType . ' command'})
+                        {  print FH_LANGUAGES ucfirst($topicTypePrefixes->{$topicType . ' command'}) . ' Ignored ';  }
+                    else
+                        {  print FH_LANGUAGES 'Ignore ';  };
+
+                    if ($topicType ne ::TOPIC_GENERAL())
+                        {  print FH_LANGUAGES NaturalDocs::Topics->TypeInfo($topicType)->Name() . ' ';  };
+
+                    my @prefixes = split(/ /, $topicTypePrefixes->{$topicType}, 2);
+
+                    if (scalar @prefixes == 1)
+                        {  print FH_LANGUAGES 'Prefix in Index: ';  }
+                    else
+                        {  print FH_LANGUAGES 'Prefixes in Index: ';  };
+
+                    print FH_LANGUAGES $topicTypePrefixes->{$topicType} . "\n";
+
+                    $usedTopicTypes{$topicType} = 1;
+                    };
+                };
+            };
+
+        if (exists $properties->{'line comments'})
+            {
+            my @comments = split(/ /, $properties->{'line comments'}, 2);
+
+            if (scalar @comments == 1)
+                {  print FH_LANGUAGES '   Line Comment: ';  }
+            else
+                {  print FH_LANGUAGES '   Line Comments: ';  };
+
+            print FH_LANGUAGES $properties->{'line comments'} . "\n";
+            };
+
+        if (exists $properties->{'block comments'})
+            {
+            my @comments = split(/ /, $properties->{'block comments'}, 3);
+
+            if (scalar @comments == 2)
+                {  print FH_LANGUAGES '   Block Comment: ';  }
+            else
+                {  print FH_LANGUAGES '   Block Comments: ';  };
+
+            print FH_LANGUAGES $properties->{'block comments'} . "\n";
+            };
+
+        if (exists $properties->{'package separator'})
+            {
+            # Prior to 1.32, Package Separator was allowed for full language support.  Ignore it when reformatting.
+            if ($version >= NaturalDocs::Version->FromString('1.32') || !exists $properties->{'full language support'})
+                {  print FH_LANGUAGES '   Package Separator: ' . $properties->{'package separator'} . "\n";  };
+            };
+
+        if (exists $properties->{'enum values'})
+            {
+            print FH_LANGUAGES '   Enum Values: ' . ucfirst(lc($properties->{'enum values'})) . "\n";
+            };
+
+        if (exists $properties->{'prototype enders'})
+            {
+            my $topicTypeEnders = $properties->{'prototype enders'};
+
+            my %usedTopicTypes;
+            my @topicTypes = ( @topicTypeOrder, keys %$topicTypeEnders );
+
+            foreach my $topicType (@topicTypes)
+                {
+                if ($topicType !~ / command$/ &&
+                    exists $topicTypeEnders->{$topicType} &&
+                    !exists $usedTopicTypes{$topicType})
+                    {
+                    print FH_LANGUAGES '   ';
+
+                    if ($topicType ne ::TOPIC_GENERAL())
+                        {  print FH_LANGUAGES NaturalDocs::Topics->TypeInfo($topicType)->Name() . ' ';  };
+
+                    my @enders = split(/ /, $topicTypeEnders->{$topicType}, 2);
+
+                    if (scalar @enders == 1)
+                        {  print FH_LANGUAGES 'Prototype Ender: ';  }
+                    else
+                        {  print FH_LANGUAGES 'Prototype Enders: ';  };
+
+                    print FH_LANGUAGES $topicTypeEnders->{$topicType} . "\n";
+
+                    $usedTopicTypes{$topicType} = 1;
+                    };
+                };
+            };
+
+        if (exists $properties->{'line extender'})
+            {
+            print FH_LANGUAGES '   Line Extender: ' . $properties->{'line extender'} . "\n";
+            };
+
+        if (exists $properties->{'perl package'})
+            {
+            print FH_LANGUAGES '   Perl Package: ' . $properties->{'perl package'} . "\n";
+            };
+
+        if (exists $properties->{'full language support'})
+            {
+            print FH_LANGUAGES '   Full Language Support: ' . $properties->{'full language support'} . "\n";
+            };
+        };
+
+    close(FH_LANGUAGES);
+    };
+
+
+
+###############################################################################
+# Group: Functions
+
+
+#
+#   Function: LanguageOf
+#
+#   Returns the language of the passed source file.
+#
+#   Parameters:
+#
+#       sourceFile - The source <FileName> to get the language of.
+#
+#   Returns:
+#
+#       A <NaturalDocs::Languages::Base>-derived object for the passed file, or undef if the file is not a recognized language.
+#
+sub LanguageOf #(sourceFile)
+    {
+    my ($self, $sourceFile) = @_;
+
+    my $extension = NaturalDocs::File->ExtensionOf($sourceFile);
+    if (defined $extension)
+        {  $extension = lc($extension);  };
+
+    my $languageName;
+
+    if (!defined $extension)
+        {  $languageName = 'shebang script';  }
+    else
+        {  $languageName = $extensions{$extension};  };
+
+    if (!defined $languageName)
+        {  $languageName = $extensions{'*'};  };
+
+    if (defined $languageName)
+        {
+        if ($languageName eq 'shebang script')
+            {
+            if (exists $shebangFiles{$sourceFile})
+                {
+                if (defined $shebangFiles{$sourceFile})
+                    {  return $languages{$shebangFiles{$sourceFile}};  }
+                else
+                    {  return undef;  };
+                }
+
+            else # (!exists $shebangFiles{$sourceFile})
+                {
+                my $shebangLine;
+
+                if (open(SOURCEFILEHANDLE, '<' . $sourceFile))
+                	{
+	                read(SOURCEFILEHANDLE, $shebangLine, 2);
+	                if ($shebangLine eq '#!')
+	                    {  $shebangLine = <SOURCEFILEHANDLE>;  }
+	                else
+	                    {  $shebangLine = undef;  };
+
+	                close (SOURCEFILEHANDLE);
+	                }
+	            elsif (defined $extension)
+	            	{  die 'Could not open ' . $sourceFile;  }
+	            # Ignore extensionless files that can't be opened.  They may be system files.
+
+                if (!defined $shebangLine)
+                    {
+                    $shebangFiles{$sourceFile} = undef;
+                    return undef;
+                    }
+                else
+                    {
+                    $shebangLine = lc($shebangLine);
+
+                    foreach my $shebangString (keys %shebangStrings)
+                        {
+                        if (index($shebangLine, $shebangString) != -1)
+                            {
+                            $shebangFiles{$sourceFile} = $shebangStrings{$shebangString};
+                            return $languages{$shebangStrings{$shebangString}};
+                            };
+                        };
+
+                    $shebangFiles{$sourceFile} = undef;
+                    return undef;
+                    };
+                };
+            }
+
+        else # language name ne 'shebang script'
+            {  return $languages{$languageName};  };
+        }
+    else # !defined $language
+        {
+        return undef;
+        };
+    };
+
+
+#
+#   Function: OnMostUsedLanguageKnown
+#
+#   Called when the most used language is known.
+#
+sub OnMostUsedLanguageKnown
+    {
+    my $self = shift;
+
+    my $language = $languages{lc( NaturalDocs::Project->MostUsedLanguage() )};
+
+    if ($language)
+        {
+        if (!$languages{'text file'}->HasIgnoredPrefixes())
+            {  $languages{'text file'}->CopyIgnoredPrefixesOf($language);  };
+        if (!$languages{'text file'}->PackageSeparatorWasSet())
+            {  $languages{'text file'}->SetPackageSeparator($language->PackageSeparator());  };
+        if (!$languages{'text file'}->EnumValuesWasSet())
+            {  $languages{'text file'}->SetEnumValues($language->EnumValues());  };
+        };
+    };
+
+
+1;

Added: trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Menu/Entry.pm
===================================================================
--- trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Menu/Entry.pm	                        (rev 0)
+++ trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Menu/Entry.pm	2009-07-01 15:30:00 UTC (rev 4275)
@@ -0,0 +1,201 @@
+###############################################################################
+#
+#   Package: NaturalDocs::Menu::Entry
+#
+###############################################################################
+#
+#   A class representing an entry in the menu.
+#
+###############################################################################
+
+# 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::Menu::Entry;
+
+
+###############################################################################
+# Group: Implementation
+
+#
+#   Constants: Members
+#
+#   The object is implemented as a blessed arrayref with the indexes below.
+#
+#       TYPE      - The <MenuEntryType>
+#       TITLE     - The title of the entry.
+#       TARGET  - The target of the entry.  If the type is <MENU_FILE>, it will be the source <FileName>.  If the type is
+#                       <MENU_LINK>, it will be the URL.  If the type is <MENU_GROUP>, it will be an arrayref of
+#                       <NaturalDocs::Menu::Entry> objects representing the group's content.  If the type is <MENU_INDEX>, it will be
+#                       a <TopicType>.
+#       FLAGS    - Any <Menu Entry Flags> that apply.
+#
+use constant TYPE => 0;
+use constant TITLE => 1;
+use constant TARGET => 2;
+use constant FLAGS => 3;
+# DEPENDENCY: New() depends on the order of these constants.
+
+
+###############################################################################
+# Group: Functions
+
+#
+#   Function: New
+#
+#   Creates and returns a new object.
+#
+#   Parameters:
+#
+#       type     - The <MenuEntryType>.
+#       title      - The title of the entry.
+#       target   - The target of the entry, if applicable.  If the type is <MENU_FILE>, use the source <FileName>.  If the type is
+#                     <MENU_LINK>, use the URL.  If the type is <MENU_INDEX>, use the <TopicType>.  Otherwise set it to undef.
+#       flags     - Any <Menu Entry Flags> that apply.
+#
+sub New #(type, title, target, flags)
+    {
+    # DEPENDENCY: This gode depends on the order of the constants.
+
+    my $package = shift;
+
+    my $object = [ @_ ];
+    bless $object, $package;
+
+    if ($object->[TYPE] == ::MENU_GROUP())
+        {  $object->[TARGET] = [ ];  };
+    if (!defined $object->[FLAGS])
+        {  $object->[FLAGS] = 0;  };
+
+    return $object;
+    };
+
+
+#   Function: Type
+#   Returns the <MenuEntryType>.
+sub Type
+    {  return $_[0]->[TYPE];  };
+
+#   Function: Title
+#   Returns the title of the entry.
+sub Title
+    {  return $_[0]->[TITLE];  };
+
+# Function: SetTitle
+# Replaces the entry's title.
+sub SetTitle #(title)
+    {  $_[0]->[TITLE] = $_[1];  };
+
+#
+#   Function: Target
+#
+#   Returns the target of the entry, if applicable.  If the type is <MENU_FILE>, it returns the source <FileName>.  If the type is
+#   <MENU_LINK>, it returns the URL.  If the type is <MENU_INDEX>, it returns the <TopicType>.  Otherwise it returns undef.
+#
+sub Target
+    {
+    my $self = shift;
+
+    # Group entries are the only time when target won't be undef when it should be.
+    if ($self->Type() == ::MENU_GROUP())
+        {  return undef;  }
+    else
+        {  return $self->[TARGET];  };
+    };
+
+# Function: SetTarget
+# Replaces the entry's target.
+sub SetTarget #(target)
+    {  $_[0]->[TARGET] = $_[1];  };
+
+#   Function: Flags
+#   Returns the <Menu Entry Flags>.
+sub Flags
+    {  return $_[0]->[FLAGS];  };
+
+# Function: SetFlags
+# Replaces the <Menu Entry Flags>.
+sub SetFlags #(flags)
+    {  $_[0]->[FLAGS] = $_[1];  };
+
+
+
+###############################################################################
+# Group: Group Functions
+#
+#   All of these functions assume the type is <MENU_GROUP>.  Do *not* call any of these without checking <Type()> first.
+
+
+#
+#   Function: GroupContent
+#
+#   Returns an arrayref of <NaturalDocs::Menu::Entry> objects representing the contents of the
+#   group, or undef otherwise.  This arrayref will always exist for <MENU_GROUP>'s and can be changed.
+#
+sub GroupContent
+    {
+    return $_[0]->[TARGET];
+    };
+
+
+#
+#   Function: GroupIsEmpty
+#
+#   If the type is <MENU_GROUP>, returns whether the group is empty.
+#
+sub GroupIsEmpty
+    {
+    my $self = shift;
+    return (scalar @{$self->GroupContent()} > 0);
+    };
+
+
+#
+#   Function: PushToGroup
+#
+#   Pushes the entry to the end of the group content.
+#
+sub PushToGroup #(entry)
+    {
+    my ($self, $entry) = @_;
+    push @{$self->GroupContent()}, $entry;
+    };
+
+
+#
+#   Function: DeleteFromGroup
+#
+#   Deletes an entry from the group content by index.
+#
+sub DeleteFromGroup #(index)
+    {
+    my ($self, $index) = @_;
+
+    my $groupContent = $self->GroupContent();
+
+    splice( @$groupContent, $index, 1 );
+    };
+
+
+#
+#   Function: MarkEndOfOriginal
+#
+#   If the group doesn't already have one, adds a <MENU_ENDOFORIGINAL> entry to the end and sets the
+#   <MENU_GROUP_HASENDOFORIGINAL> flag.
+#
+sub MarkEndOfOriginal
+    {
+    my $self = shift;
+
+    if (($self->Flags() & ::MENU_GROUP_HASENDOFORIGINAL()) == 0)
+        {
+        $self->PushToGroup( NaturalDocs::Menu::Entry->New(::MENU_ENDOFORIGINAL(), undef, undef, undef) );
+        $self->SetFlags( $self->Flags() | ::MENU_GROUP_HASENDOFORIGINAL() );
+        };
+    };
+
+
+1;

Added: trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Menu.pm
===================================================================
--- trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Menu.pm	                        (rev 0)
+++ trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Menu.pm	2009-07-01 15:30:00 UTC (rev 4275)
@@ -0,0 +1,3406 @@
+###############################################################################
+#
+#   Package: NaturalDocs::Menu
+#
+###############################################################################
+#
+#   A package handling the menu's contents and state.
+#
+#   Usage and Dependencies:
+#
+#       - The <Event Handlers> can be called by <NaturalDocs::Project> immediately.
+#
+#       - Prior to initialization, <NaturalDocs::Project> must be initialized, and all files that have been changed must be run
+#         through <NaturalDocs::Parser->ParseForInformation()>.
+#
+#       - To initialize, call <LoadAndUpdate()>.  Afterwards, all other functions are available.  Also, <LoadAndUpdate()> will
+#         call <NaturalDocs::Settings->GenerateDirectoryNames()>.
+#
+#       - To save the changes back to disk, call <Save()>.
+#
+###############################################################################
+
+# 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 NaturalDocs::Menu::Entry;
+
+use strict;
+use integer;
+
+package NaturalDocs::Menu;
+
+
+#
+#   Constants: Constants
+#
+#   MAXFILESINGROUP - The maximum number of file entries that can be present in a group before it becomes a candidate for
+#                                  sub-grouping.
+#   MINFILESINNEWGROUP - The minimum number of file entries that must be present in a group before it will be automatically
+#                                        created.  This is *not* the number of files that must be in a group before it's deleted.
+#
+use constant MAXFILESINGROUP => 6;
+use constant MINFILESINNEWGROUP => 3;
+
+
+###############################################################################
+# Group: Variables
+
+
+#
+#   bool: hasChanged
+#
+#   Whether the menu changed or not, regardless of why.
+#
+my $hasChanged;
+
+
+#
+#   Object: menu
+#
+#   The parsed menu file.  Is stored as a <MENU_GROUP> <NaturalDocs::Menu::Entry> object, with the top-level entries being
+#   stored as the group's content.  This is done because it makes a number of functions simpler to implement, plus it allows group
+#   flags to be set on the top-level.  However, it is exposed externally via <Content()> as an arrayref.
+#
+#   This structure will only contain objects for <MENU_FILE>, <MENU_GROUP>, <MENU_TEXT>, <MENU_LINK>, and
+#   <MENU_INDEX> entries.  Other types, such as <MENU_TITLE>, are stored in variables such as <title>.
+#
+my $menu;
+
+#
+#   hash: defaultTitlesChanged
+#
+#   An existence hash of default titles that have changed, since <OnDefaultTitleChange()> will be called before
+#   <LoadAndUpdate()>.  Collects them to be applied later.  The keys are the <FileNames>.
+#
+my %defaultTitlesChanged;
+
+#
+#   String: title
+#
+#   The title of the menu.
+#
+my $title;
+
+#
+#   String: subTitle
+#
+#   The sub-title of the menu.
+#
+my $subTitle;
+
+#
+#   String: footer
+#
+#   The footer for the documentation.
+#
+my $footer;
+
+#
+#   String: timestampText
+#
+#   The timestamp for the documentation, stored as the final output text.
+#
+my $timestampText;
+
+#
+#   String: timestampCode
+#
+#   The timestamp for the documentation, storted as the symbolic code.
+#
+my $timestampCode;
+
+#
+#   hash: indexes
+#
+#   An existence hash of all the defined index <TopicTypes> appearing in the menu.
+#
+my %indexes;
+
+#
+#   hash: previousIndexes
+#
+#   An existence hash of all the index <TopicTypes> that appeared in the menu last time.
+#
+my %previousIndexes;
+
+#
+#   hash: bannedIndexes
+#
+#   An existence hash of all the index <TopicTypes> that the user has manually deleted, and thus should not be added back to
+#   the menu automatically.
+#
+my %bannedIndexes;
+
+
+###############################################################################
+# Group: Files
+
+#
+#   File: Menu.txt
+#
+#   The file used to generate the menu.
+#
+#   Format:
+#
+#       The file is plain text.  Blank lines can appear anywhere and are ignored.  Tags and their content must be completely
+#       contained on one line with the exception of Group's braces.  All values in brackets below are encoded with entity characters.
+#
+#       > # [comment]
+#
+#       The file supports single-line comments via #.  They can appear alone on a line or after content.
+#
+#       > Format: [version]
+#       > Title: [title]
+#       > SubTitle: [subtitle]
+#       > Footer: [footer]
+#       > Timestamp: [timestamp code]
+#
+#       The file format version, menu title, subtitle, footer, and timestamp are specified as above.  Each can only be specified once,
+#       with subsequent ones being ignored.  Subtitle is ignored if Title is not present.  Format must be the first entry in the file.  If
+#       it's not present, it's assumed the menu is from version 0.95 or earlier, since it was added with 1.0.
+#
+#       The timestamp code is as follows.
+#
+#           m - Single digit month, where applicable.  January is "1".
+#           mm - Always double digit month.  January is "01".
+#           mon - Short month word.  January is "Jan".
+#           month - Long month word.  January is "January".
+#           d - Single digit day, where applicable.  1 is "1".
+#           dd - Always double digit day.  1 is "01".
+#           day - Day with text extension.  1 is "1st".
+#           yy - Double digit year.  2006 is "06".
+#           yyyy - Four digit year.  2006 is "2006".
+#           year - Four digit year.  2006 is "2006".
+#
+#       Anything else is left literal in the output.
+#
+#       > File: [title] ([file name])
+#       > File: [title] (auto-title, [file name])
+#       > File: [title] (no auto-title, [file name])
+#
+#       Files are specified as above.  If there is only one input directory, file names are relative.  Otherwise they are absolute.
+#       If "no auto-title" is specified, the title on the line is used.  If not, the title is ignored and the
+#       default file title is used instead.  Auto-title defaults to on, so specifying "auto-title" is for compatibility only.
+#
+#       > Group: [title]
+#       > Group: [title] { ... }
+#
+#       Groups are specified as above.  If no braces are specified, the group's content is everything that follows until the end of the
+#       file, the next group (braced or unbraced), or the closing brace of a parent group.  Group braces are the only things in this
+#       file that can span multiple lines.
+#
+#       There is no limitations on where the braces can appear.  The opening brace can appear after the group tag, on its own line,
+#       or preceding another tag on a line.  Similarly, the closing brace can appear after another tag or on its own line.  Being
+#       bitchy here would just get in the way of quick and dirty editing; the package will clean it up automatically when it writes it
+#       back to disk.
+#
+#       > Text: [text]
+#
+#       Arbitrary text is specified as above.  As with other tags, everything must be contained on the same line.
+#
+#       > Link: [URL]
+#       > Link: [title] ([URL])
+#
+#       External links can be specified as above.  If the titled form is not used, the URL is used as the title.
+#
+#       > Index: [name]
+#       > [topic type name] Index: [name]
+#
+#       Indexes are specified as above.  The topic type names can be either singular or plural.  General is assumed if not specified.
+#
+#       > Don't Index: [topic type name]
+#       > Don't Index: [topic type name], [topic type name], ...
+#
+#       The option above prevents indexes that exist but are not on the menu from being automatically added.
+#
+#       > Data: [number]([obscured data])
+#
+#       Used to store non-user editable data.
+#
+#       > Data: 1([obscured: [directory name]///[input directory]])
+#
+#       When there is more than one directory, these lines store the input directories used in the last run and their names.  This
+#       allows menu files to be shared across machines since the names will be consistent and the directories can be used to convert
+#       filenames to the local machine's paths.  We don't want this user-editable because they may think changing it changes the
+#       input directories, when it doesn't.  Also, changing it without changing all the paths screws up resolving.
+#
+#       > Data: 2([obscured: [directory name])
+#
+#       When there is only one directory and its name is not "default", this stores the name.
+#
+#
+#   Entities:
+#
+#       &amp; - Ampersand.
+#       &lparen; - Left parenthesis.
+#       &rparen; - Right parenthesis.
+#       &lbrace; - Left brace.
+#       &rbrace; - Right brace.
+#
+#
+#   Revisions:
+#
+#       1.4:
+#
+#           - Added Timestamp property.
+#           - Values are now encoded with entity characters.
+#
+#       1.3:
+#
+#           - File names are now relative again if there is only one input directory.
+#           - Data: 2(...) added.
+#           - Can't use synonyms like "copyright" for "footer" or "sub-title" for "subtitle".
+#           - "Don't Index" line now requires commas to separate them, whereas it tolerated just spaces before.
+#
+#       1.16:
+#
+#           - File names are now absolute instead of relative.  Prior to 1.16 only one input directory was allowed, so they could be
+#             relative.
+#           - Data keywords introduced to store input directories and their names.
+#
+#       1.14:
+#
+#           - Renamed this file from NaturalDocs_Menu.txt to Menu.txt.
+#
+#       1.1:
+#
+#           - Added the "don't index" line.
+#
+#           This is also the point where indexes were automatically added and removed, so all index entries from prior revisions
+#           were manually added and are not guaranteed to contain anything.
+#
+#       1.0:
+#
+#           - Added the format line.
+#           - Added the "no auto-title" attribute.
+#           - Changed the file entry default to auto-title.
+#
+#           This is also the point where auto-organization and better auto-titles were introduced.  All groups in prior revisions were
+#           manually added, with the exception of a top-level Other group where new files were automatically added if there were
+#           groups defined.
+#
+#       Break in support:
+#
+#           Releases prior to 1.0 are no longer supported.  Why?
+#
+#           - They don't have a Format: line, which is required by <NaturalDocs::ConfigFile>, although I could work around this
+#             if I needed to.
+#           - No significant number of downloads for pre-1.0 releases.
+#           - Code simplification.  I don't have to bridge the conversion from manual-only menu organization to automatic.
+#
+#       0.9:
+#
+#           - Added index entries.
+#
+
+#
+#   File: PreviousMenuState.nd
+#
+#   The file used to store the previous state of the menu so as to detect changes.
+#
+#
+#   Format:
+#
+#   > [BINARY_FORMAT]
+#   > [VersionInt: app version]
+#
+#   First is the standard <BINARY_FORMAT> <VersionInt> header.
+#
+#   > [UInt8: 0 (end group)]
+#   > [UInt8: MENU_FILE] [UInt8: noAutoTitle] [AString16: title] [AString16: target]
+#   > [UInt8: MENU_GROUP] [AString16: title]
+#   > [UInt8: MENU_INDEX] [AString16: title] [AString16: topic type]
+#   > [UInt8: MENU_LINK] [AString16: title] [AString16: url]
+#   > [UInt8: MENU_TEXT] [AString16: text]
+#
+#   The first UInt8 of each following line is either zero or one of the <Menu Entry Types>.  What follows is contextual.
+#
+#   There are no entries for title, subtitle, or footer.  Only the entries present in <menu>.
+#
+#   See Also:
+#
+#       <File Format Conventions>
+#
+#   Dependencies:
+#
+#       - Because the type is represented by a UInt8, the <Menu Entry Types> must all be <= 255.
+#
+#   Revisions:
+#
+#       1.3:
+#
+#           - The topic type following the <MENU_INDEX> entries were changed from UInt8s to AString16s, since <TopicTypes>
+#             were switched from integer constants to strings.  You can still convert the old to the new via
+#             <NaturalDocs::Topics->TypeFromLegacy()>.
+#
+#       1.16:
+#
+#           - The file targets are now absolute.  Prior to 1.16, they were relative to the input directory since only one was allowed.
+#
+#       1.14:
+#
+#           - The file was renamed from NaturalDocs.m to PreviousMenuState.nd and moved into the Data subdirectory.
+#
+#       1.0:
+#
+#           - The file's format was completely redone.  Prior to 1.0, the file was a text file consisting of the app version and a line
+#             which was a tab-separated list of the indexes present in the menu.  * meant the general index.
+#
+#       Break in support:
+#
+#           Pre-1.0 files are no longer supported.  There was no significant number of downloads for pre-1.0 releases, and this
+#           eliminates a separate code path for them.
+#
+#       0.95:
+#
+#           - Change the file version to match the app version.  Prior to 0.95, the version line was 1.  Test for "1" instead of "1.0" to
+#             distinguish.
+#
+#       0.9:
+#
+#           - The file was added to the project.  Prior to 0.9, it didn't exist.
+#
+
+
+###############################################################################
+# Group: File Functions
+
+#
+#   Function: LoadAndUpdate
+#
+#   Loads the menu file from disk and updates it.  Will add, remove, rearrange, and remove auto-titling from entries as
+#   necessary.  Will also call <NaturalDocs::Settings->GenerateDirectoryNames()>.
+#
+sub LoadAndUpdate
+    {
+    my ($self) = @_;
+
+    my ($inputDirectoryNames, $relativeFiles, $onlyDirectoryName) = $self->LoadMenuFile();
+
+    my $errorCount = NaturalDocs::ConfigFile->ErrorCount();
+    if ($errorCount)
+        {
+        NaturalDocs::ConfigFile->PrintErrorsAndAnnotateFile();
+        NaturalDocs::Error->SoftDeath('There ' . ($errorCount == 1 ? 'is an error' : 'are ' . $errorCount . ' errors')
+                                                    . ' in ' . NaturalDocs::Project->UserConfigFile('Menu.txt'));
+        };
+
+    # If the menu has a timestamp and today is a different day than the last time Natural Docs was run, we have to count it as the
+    # menu changing.
+    if (defined $timestampCode)
+        {
+        my (undef, undef, undef, $currentDay, $currentMonth, $currentYear) = localtime();
+        my (undef, undef, undef, $lastDay, $lastMonth, $lastYear) =
+            localtime( (stat( NaturalDocs::Project->DataFile('PreviousMenuState.nd') ))[9] );
+            # This should be okay if the previous menu state file doesn't exist.
+
+        if ($currentDay != $lastDay || $currentMonth != $lastMonth || $currentYear != $lastYear)
+            {  $hasChanged = 1;  };
+        };
+
+
+    if ($relativeFiles)
+        {
+        my $inputDirectory = $self->ResolveRelativeInputDirectories($onlyDirectoryName);
+
+        if ($onlyDirectoryName)
+            {  $inputDirectoryNames = { $inputDirectory => $onlyDirectoryName };  };
+        }
+    else
+        {  $self->ResolveInputDirectories($inputDirectoryNames);  };
+
+    NaturalDocs::Settings->GenerateDirectoryNames($inputDirectoryNames);
+
+    my $filesInMenu = $self->FilesInMenu();
+
+    my ($previousMenu, $previousIndexes, $previousFiles) = $self->LoadPreviousMenuStateFile();
+
+    if (defined $previousIndexes)
+        {  %previousIndexes = %$previousIndexes;  };
+
+    if (defined $previousFiles)
+        {  $self->LockUserTitleChanges($previousFiles);  };
+
+    # Don't need these anymore.  We keep this level of detail because it may be used more in the future.
+    $previousMenu = undef;
+    $previousFiles = undef;
+    $previousIndexes = undef;
+
+    # We flag title changes instead of actually performing them at this point for two reasons.  First, contents of groups are still
+    # subject to change, which would affect the generated titles.  Second, we haven't detected the sort order yet.  Changing titles
+    # could make groups appear unalphabetized when they were beforehand.
+
+    my $updateAllTitles;
+
+    # If the menu file changed, we can't be sure which groups changed and which didn't without a comparison, which really isn't
+    # worth the trouble.  So we regenerate all the titles instead.
+    if (NaturalDocs::Project->UserConfigFileStatus('Menu.txt') == ::FILE_CHANGED())
+        {  $updateAllTitles = 1;  }
+    else
+        {  $self->FlagAutoTitleChanges();  };
+
+    # We add new files before deleting old files so their presence still affects the grouping.  If we deleted old files first, it could
+    # throw off where to place the new ones.
+
+    $self->AutoPlaceNewFiles($filesInMenu);
+
+    my $numberRemoved = $self->RemoveDeadFiles();
+
+    $self->CheckForTrashedMenu(scalar keys %$filesInMenu, $numberRemoved);
+
+    # Don't ban indexes if they deleted Menu.txt.  They may have not deleted PreviousMenuState.nd and we don't want everything
+    # to be banned because of it.
+    if (NaturalDocs::Project->UserConfigFileStatus('Menu.txt') != ::FILE_DOESNTEXIST())
+        {  $self->BanAndUnbanIndexes();  };
+
+    # Index groups need to be detected before adding new ones.
+
+    $self->DetectIndexGroups();
+
+    $self->AddAndRemoveIndexes();
+
+   # We wait until after new files are placed to remove dead groups because a new file may save a group.
+
+    $self->RemoveDeadGroups();
+
+    $self->CreateDirectorySubGroups();
+
+    # We detect the sort before regenerating the titles so it doesn't get thrown off by changes.  However, we do it after deleting
+    # dead entries and moving things into subgroups because their removal may bump it into a stronger sort category (i.e.
+    # SORTFILESANDGROUPS instead of just SORTFILES.)  New additions don't factor into the sort.
+
+    $self->DetectOrder($updateAllTitles);
+
+    $self->GenerateAutoFileTitles($updateAllTitles);
+
+    $self->ResortGroups($updateAllTitles);
+
+
+    # Don't need this anymore.
+    %defaultTitlesChanged = ( );
+    };
+
+
+#
+#   Function: Save
+#
+#   Writes the changes to the menu files.
+#
+sub Save
+    {
+    my ($self) = @_;
+
+    if ($hasChanged)
+        {
+        $self->SaveMenuFile();
+        $self->SavePreviousMenuStateFile();
+        };
+    };
+
+
+###############################################################################
+# Group: Information Functions
+
+#
+#   Function: HasChanged
+#
+#   Returns whether the menu has changed or not.
+#
+sub HasChanged
+    {  return $hasChanged;  };
+
+#
+#   Function: Content
+#
+#   Returns the parsed menu as an arrayref of <NaturalDocs::Menu::Entry> objects.  Do not change the arrayref.
+#
+#   The arrayref will only contain <MENU_FILE>, <MENU_GROUP>, <MENU_INDEX>, <MENU_TEXT>, and <MENU_LINK>
+#   entries.  Entries such as <MENU_TITLE> are parsed out and are only accessible via functions such as <Title()>.
+#
+sub Content
+    {  return $menu->GroupContent();  };
+
+#
+#   Function: Title
+#
+#   Returns the title of the menu, or undef if none.
+#
+sub Title
+    {  return $title;  };
+
+#
+#   Function: SubTitle
+#
+#   Returns the sub-title of the menu, or undef if none.
+#
+sub SubTitle
+    {  return $subTitle;  };
+
+#
+#   Function: Footer
+#
+#   Returns the footer of the documentation, or undef if none.
+#
+sub Footer
+    {  return $footer;  };
+
+#
+#   Function: TimeStamp
+#
+#   Returns the timestamp text of the documentation, or undef if none.
+#
+sub TimeStamp
+    {  return $timestampText;  };
+
+#
+#   Function: Indexes
+#
+#   Returns an existence hashref of all the index <TopicTypes> appearing in the menu.  Do not change the hashref.
+#
+sub Indexes
+    {  return \%indexes;  };
+
+#
+#   Function: PreviousIndexes
+#
+#   Returns an existence hashref of all the index <TopicTypes> that previously appeared in the menu.  Do not change the
+#   hashref.
+#
+sub PreviousIndexes
+    {  return \%previousIndexes;  };
+
+
+#
+#   Function: FilesInMenu
+#
+#   Returns a hashref of all the files present in the menu.  The keys are the <FileNames>, and the values are references to their
+#   <NaturalDocs::Menu::Entry> objects.
+#
+sub FilesInMenu
+    {
+    my ($self) = @_;
+
+    my @groupStack = ( $menu );
+    my $filesInMenu = { };
+
+    while (scalar @groupStack)
+        {
+        my $currentGroup = pop @groupStack;
+        my $currentGroupContent = $currentGroup->GroupContent();
+
+        foreach my $entry (@$currentGroupContent)
+            {
+            if ($entry->Type() == ::MENU_GROUP())
+                {  push @groupStack, $entry;  }
+            elsif ($entry->Type() == ::MENU_FILE())
+                {  $filesInMenu->{ $entry->Target() } = $entry;  };
+            };
+        };
+
+    return $filesInMenu;
+    };
+
+
+
+###############################################################################
+# Group: Event Handlers
+#
+#   These functions are called by <NaturalDocs::Project> only.  You don't need to worry about calling them.  For example, when
+#   changing the default menu title of a file, you only need to call <NaturalDocs::Project->SetDefaultMenuTitle()>.  That function
+#   will handle calling <OnDefaultTitleChange()>.
+
+
+#
+#   Function: OnDefaultTitleChange
+#
+#   Called by <NaturalDocs::Project> if the default menu title of a source file has changed.
+#
+#   Parameters:
+#
+#       file    - The source <FileName> that had its default menu title changed.
+#
+sub OnDefaultTitleChange #(file)
+    {
+    my ($self, $file) = @_;
+
+    # Collect them for later.  We'll deal with them in LoadAndUpdate().
+
+    $defaultTitlesChanged{$file} = 1;
+    };
+
+
+
+###############################################################################
+# Group: Support Functions
+
+
+#
+#   Function: LoadMenuFile
+#
+#   Loads and parses the menu file <Menu.txt>.  This will fill <menu>, <title>, <subTitle>, <footer>, <timestampText>,
+#   <timestampCode>, <indexes>, and <bannedIndexes>.  If there are any errors in the file, they will be recorded with
+#   <NaturalDocs::ConfigFile->AddError()>.
+#
+#   Returns:
+#
+#       The array ( inputDirectories, relativeFiles, onlyDirectoryName ) or an empty array if the file doesn't exist.
+#
+#       inputDirectories - A hashref of all the input directories and their names stored in the menu file.  The keys are the
+#                                 directories and the values are their names.  Undef if none.
+#       relativeFiles - Whether the menu uses relative file names.
+#       onlyDirectoryName - The name of the input directory if there is only one.
+#
+sub LoadMenuFile
+    {
+    my ($self) = @_;
+
+    my $inputDirectories = { };
+    my $relativeFiles;
+    my $onlyDirectoryName;
+
+    # A stack of Menu::Entry object references as we move through the groups.
+    my @groupStack;
+
+    $menu = NaturalDocs::Menu::Entry->New(::MENU_GROUP(), undef, undef, undef);
+    my $currentGroup = $menu;
+
+    # Whether we're currently in a braceless group, since we'd have to find the implied end rather than an explicit one.
+    my $inBracelessGroup;
+
+    # Whether we're right after a group token, which is the only place there can be an opening brace.
+    my $afterGroupToken;
+
+    my $version;
+
+    if ($version = NaturalDocs::ConfigFile->Open(NaturalDocs::Project->UserConfigFile('Menu.txt'), 1))
+        {
+        # We don't check if the menu file is from a future version because we can't just throw it out and regenerate it like we can
+        # with other data files.  So we just keep going regardless.  Any syntactic differences will show up as errors.
+
+        while (my ($keyword, $value, $comment) = NaturalDocs::ConfigFile->GetLine())
+            {
+            # Check for an opening brace after a group token.  This has to be separate from the rest of the code because the flag
+            # needs to be reset after every line.
+            if ($afterGroupToken)
+                {
+                $afterGroupToken = undef;
+
+                if ($keyword eq '{')
+                    {
+                    $inBracelessGroup = undef;
+                    next;
+                    }
+                else
+                    {  $inBracelessGroup = 1;  };
+                };
+
+
+            # Now on to the real code.
+
+            if ($keyword eq 'file')
+                {
+                my $flags = 0;
+
+                if ($value =~ /^(.+)\(([^\(]+)\)$/)
+                    {
+                    my ($title, $file) = ($1, $2);
+
+                    $title =~ s/ +$//;
+
+                    # Check for auto-title modifier.
+                    if ($file =~ /^((?:no )?auto-title, ?)(.+)$/i)
+                        {
+                        my $modifier;
+                        ($modifier, $file) = ($1, $2);
+
+                        if ($modifier =~ /^no/i)
+                            {  $flags |= ::MENU_FILE_NOAUTOTITLE();  };
+                        };
+
+                    my $entry = NaturalDocs::Menu::Entry->New(::MENU_FILE(), $self->RestoreAmpChars($title),
+                                                                                       $self->RestoreAmpChars($file), $flags);
+
+                    $currentGroup->PushToGroup($entry);
+                    }
+                else
+                    {  NaturalDocs::ConfigFile->AddError('File lines must be in the format "File: [title] ([location])"');  };
+                }
+
+
+            elsif ($keyword eq 'group')
+                {
+                # End a braceless group, if we were in one.
+                if ($inBracelessGroup)
+                    {
+                    $currentGroup = pop @groupStack;
+                    $inBracelessGroup = undef;
+                    };
+
+                my $entry = NaturalDocs::Menu::Entry->New(::MENU_GROUP(), $self->RestoreAmpChars($value), undef, undef);
+
+                $currentGroup->PushToGroup($entry);
+
+                push @groupStack, $currentGroup;
+                $currentGroup = $entry;
+
+                $afterGroupToken = 1;
+                }
+
+
+            elsif ($keyword eq '{')
+                {
+                NaturalDocs::ConfigFile->AddError('Opening braces are only allowed after Group tags.');
+                }
+
+
+            elsif ($keyword eq '}')
+                {
+                # End a braceless group, if we were in one.
+                if ($inBracelessGroup)
+                    {
+                    $currentGroup = pop @groupStack;
+                    $inBracelessGroup = undef;
+                    };
+
+                # End a braced group too.
+                if (scalar @groupStack)
+                    {  $currentGroup = pop @groupStack;  }
+                else
+                    {  NaturalDocs::ConfigFile->AddError('Unmatched closing brace.');  };
+                }
+
+
+            elsif ($keyword eq 'title')
+                {
+                if (!defined $title)
+                    {  $title = $self->RestoreAmpChars($value);  }
+                else
+                    {  NaturalDocs::ConfigFile->AddError('Title can only be defined once.');  };
+                }
+
+
+            elsif ($keyword eq 'subtitle')
+                {
+                if (defined $title)
+                    {
+                    if (!defined $subTitle)
+                        {  $subTitle = $self->RestoreAmpChars($value);  }
+                    else
+                        {  NaturalDocs::ConfigFile->AddError('SubTitle can only be defined once.');  };
+                    }
+                else
+                    {  NaturalDocs::ConfigFile->AddError('Title must be defined before SubTitle.');  };
+                }
+
+
+            elsif ($keyword eq 'footer')
+                {
+                if (!defined $footer)
+                    {  $footer = $self->RestoreAmpChars($value);  }
+                else
+                    {  NaturalDocs::ConfigFile->AddError('Footer can only be defined once.');  };
+                }
+
+
+            elsif ($keyword eq 'timestamp')
+                {
+                if (!defined $timestampCode)
+                    {
+                    $timestampCode = $self->RestoreAmpChars($value);
+                    $self->GenerateTimestampText();
+                    }
+                else
+                    {  NaturalDocs::ConfigFile->AddError('Timestamp can only be defined once.');  };
+                }
+
+
+            elsif ($keyword eq 'text')
+                {
+                $currentGroup->PushToGroup( NaturalDocs::Menu::Entry->New(::MENU_TEXT(), $self->RestoreAmpChars($value),
+                                                                                                              undef, undef) );
+                }
+
+
+            elsif ($keyword eq 'link')
+                {
+                my ($title, $url);
+
+                if ($value =~ /^([^\(\)]+?) ?\(([^\)]+)\)$/)
+                    {
+                    ($title, $url) = ($1, $2);
+                    }
+                elsif (defined $comment)
+                    {
+                    $value .= $comment;
+
+                    if ($value =~ /^([^\(\)]+?) ?\(([^\)]+)\) ?(?:#.*)?$/)
+                        {
+                        ($title, $url) = ($1, $2);
+                        };
+                    };
+
+                if ($title)
+                    {
+                    $currentGroup->PushToGroup( NaturalDocs::Menu::Entry->New(::MENU_LINK(), $self->RestoreAmpChars($title),
+                                                                 $self->RestoreAmpChars($url), undef) );
+                    }
+                else
+                    {  NaturalDocs::ConfigFile->AddError('Link lines must be in the format "Link: [title] ([url])"');  };
+                }
+
+
+            elsif ($keyword eq 'data')
+                {
+                $value =~ /^(\d)\((.*)\)$/;
+                my ($number, $data) = ($1, $2);
+
+                $data = NaturalDocs::ConfigFile->Unobscure($data);
+
+                # The input directory naming convention changed with version 1.32, but NaturalDocs::Settings will handle that
+                # automatically.
+
+                if ($number == 1)
+                    {
+                    my ($dirName, $inputDir) = split(/\/\/\//, $data, 2);
+                    $inputDirectories->{$inputDir} = $dirName;
+                    }
+                elsif ($number == 2)
+                    {  $onlyDirectoryName = $data;  };
+                # Ignore other numbers because it may be from a future format and we don't want to make the user delete it
+                # manually.
+                }
+
+            elsif ($keyword eq "don't index")
+                {
+                my @indexes = split(/, ?/, $value);
+
+                foreach my $index (@indexes)
+                    {
+                    my $indexType = NaturalDocs::Topics->TypeFromName( $self->RestoreAmpChars($index) );
+
+                    if (defined $indexType)
+                        {  $bannedIndexes{$indexType} = 1;  };
+                    };
+                }
+
+            elsif ($keyword eq 'index')
+                {
+                my $entry = NaturalDocs::Menu::Entry->New(::MENU_INDEX(), $self->RestoreAmpChars($value),
+                                                                                   ::TOPIC_GENERAL(), undef);
+                $currentGroup->PushToGroup($entry);
+
+                $indexes{::TOPIC_GENERAL()} = 1;
+                }
+
+            elsif (substr($keyword, -6) eq ' index')
+                {
+                my $index = substr($keyword, 0, -6);
+                my ($indexType, $indexInfo) = NaturalDocs::Topics->NameInfo( $self->RestoreAmpChars($index) );
+
+                if (defined $indexType)
+                    {
+                    if ($indexInfo->Index())
+                        {
+                        $indexes{$indexType} = 1;
+                        $currentGroup->PushToGroup(
+                            NaturalDocs::Menu::Entry->New(::MENU_INDEX(), $self->RestoreAmpChars($value), $indexType, undef) );
+                        }
+                    else
+                        {
+                        # If it's on the menu but isn't indexable, the topic setting may have changed out from under it.
+                        $hasChanged = 1;
+                        };
+                    }
+                else
+                    {
+                    NaturalDocs::ConfigFile->AddError($index . ' is not a valid index type.');
+                    };
+                }
+
+            else
+                {
+                NaturalDocs::ConfigFile->AddError(ucfirst($keyword) . ' is not a valid keyword.');
+                };
+            };
+
+
+        # End a braceless group, if we were in one.
+        if ($inBracelessGroup)
+            {
+            $currentGroup = pop @groupStack;
+            $inBracelessGroup = undef;
+            };
+
+        # Close up all open groups.
+        my $openGroups = 0;
+        while (scalar @groupStack)
+            {
+            $currentGroup = pop @groupStack;
+            $openGroups++;
+            };
+
+        if ($openGroups == 1)
+            {  NaturalDocs::ConfigFile->AddError('There is an unclosed group.');  }
+        elsif ($openGroups > 1)
+            {  NaturalDocs::ConfigFile->AddError('There are ' . $openGroups . ' unclosed groups.');  };
+
+
+        if (!scalar keys %$inputDirectories)
+            {
+            $inputDirectories = undef;
+            $relativeFiles = 1;
+            };
+
+        NaturalDocs::ConfigFile->Close();
+
+        return ($inputDirectories, $relativeFiles, $onlyDirectoryName);
+        }
+
+    else
+        {  return ( );  };
+    };
+
+
+#
+#   Function: SaveMenuFile
+#
+#   Saves the current menu to <Menu.txt>.
+#
+sub SaveMenuFile
+    {
+    my ($self) = @_;
+
+    open(MENUFILEHANDLE, '>' . NaturalDocs::Project->UserConfigFile('Menu.txt'))
+        or die "Couldn't save menu file " . NaturalDocs::Project->UserConfigFile('Menu.txt') . "\n";
+
+
+    print MENUFILEHANDLE
+    "Format: " . NaturalDocs::Settings->TextAppVersion() . "\n\n\n";
+
+    my $inputDirs = NaturalDocs::Settings->InputDirectories();
+
+
+    if (defined $title)
+        {
+        print MENUFILEHANDLE 'Title: ' . $self->ConvertAmpChars($title) . "\n";
+
+        if (defined $subTitle)
+            {
+            print MENUFILEHANDLE 'SubTitle: ' . $self->ConvertAmpChars($subTitle) . "\n";
+            }
+        else
+            {
+            print MENUFILEHANDLE
+            "\n"
+            . "# You can also add a sub-title to your menu like this:\n"
+            . "# SubTitle: [subtitle]\n";
+            };
+        }
+    else
+        {
+        print MENUFILEHANDLE
+        "# You can add a title and sub-title to your menu like this:\n"
+        . "# Title: [project name]\n"
+        . "# SubTitle: [subtitle]\n";
+        };
+
+    print MENUFILEHANDLE "\n";
+
+    if (defined $footer)
+        {
+        print MENUFILEHANDLE 'Footer: ' . $self->ConvertAmpChars($footer) . "\n";
+        }
+    else
+        {
+        print MENUFILEHANDLE
+        "# You can add a footer to your documentation like this:\n"
+        . "# Footer: [text]\n"
+        . "# If you want to add a copyright notice, this would be the place to do it.\n";
+        };
+
+    if (defined $timestampCode)
+        {
+        print MENUFILEHANDLE 'Timestamp: ' . $self->ConvertAmpChars($timestampCode) . "\n";
+        }
+    else
+        {
+        print MENUFILEHANDLE
+        "\n"
+        . "# You can add a timestamp to your documentation like one of these:\n"
+        . "# Timestamp: Generated on month day, year\n"
+        . "# Timestamp: Updated mm/dd/yyyy\n"
+        . "# Timestamp: Last updated mon day\n"
+        . "#\n";
+        };
+
+    print MENUFILEHANDLE
+        qq{#   m     - One or two digit month.  January is "1"\n}
+        . qq{#   mm    - Always two digit month.  January is "01"\n}
+        . qq{#   mon   - Short month word.  January is "Jan"\n}
+        . qq{#   month - Long month word.  January is "January"\n}
+        . qq{#   d     - One or two digit day.  1 is "1"\n}
+        . qq{#   dd    - Always two digit day.  1 is "01"\n}
+        . qq{#   day   - Day with letter extension.  1 is "1st"\n}
+        . qq{#   yy    - Two digit year.  2006 is "06"\n}
+        . qq{#   yyyy  - Four digit year.  2006 is "2006"\n}
+        . qq{#   year  - Four digit year.  2006 is "2006"\n}
+
+        . "\n";
+
+    if (scalar keys %bannedIndexes)
+        {
+        print MENUFILEHANDLE
+
+        "# These are indexes you deleted, so Natural Docs will not add them again\n"
+        . "# unless you remove them from this line.\n"
+        . "\n"
+        . "Don't Index: ";
+
+        my $first = 1;
+
+        foreach my $index (keys %bannedIndexes)
+            {
+            if (!$first)
+                {  print MENUFILEHANDLE ', ';  }
+            else
+                {  $first = undef;  };
+
+            print MENUFILEHANDLE $self->ConvertAmpChars( NaturalDocs::Topics->NameOfType($index, 1), CONVERT_COMMAS() );
+            };
+
+        print MENUFILEHANDLE "\n\n";
+        };
+
+
+    # Remember to keep lines below eighty characters.
+
+    print MENUFILEHANDLE
+    "\n"
+    . "# --------------------------------------------------------------------------\n"
+    . "# \n"
+    . "# Cut and paste the lines below to change the order in which your files\n"
+    . "# appear on the menu.  Don't worry about adding or removing files, Natural\n"
+    . "# Docs will take care of that.\n"
+    . "# \n"
+    . "# You can further organize the menu by grouping the entries.  Add a\n"
+    . "# \"Group: [name] {\" line to start a group, and add a \"}\" to end it.\n"
+    . "# \n"
+    . "# You can add text and web links to the menu by adding \"Text: [text]\" and\n"
+    . "# \"Link: [name] ([URL])\" lines, respectively.\n"
+    . "# \n"
+    . "# The formatting and comments are auto-generated, so don't worry about\n"
+    . "# neatness when editing the file.  Natural Docs will clean it up the next\n"
+    . "# time it is run.  When working with groups, just deal with the braces and\n"
+    . "# forget about the indentation and comments.\n"
+    . "# \n";
+
+    if (scalar @$inputDirs > 1)
+        {
+        print MENUFILEHANDLE
+        "# You can use this file on other computers even if they use different\n"
+        . "# directories.  As long as the command line points to the same source files,\n"
+        . "# Natural Docs will be able to correct the locations automatically.\n"
+        . "# \n";
+        };
+
+    print MENUFILEHANDLE
+    "# --------------------------------------------------------------------------\n"
+
+    . "\n\n";
+
+
+    $self->WriteMenuEntries($menu->GroupContent(), \*MENUFILEHANDLE, undef, (scalar @$inputDirs == 1));
+
+
+    if (scalar @$inputDirs > 1)
+        {
+        print MENUFILEHANDLE
+        "\n\n##### Do not change or remove these lines. #####\n";
+
+        foreach my $inputDir (@$inputDirs)
+            {
+            print MENUFILEHANDLE
+            'Data: 1(' . NaturalDocs::ConfigFile->Obscure( NaturalDocs::Settings->InputDirectoryNameOf($inputDir)
+                                                                              . '///' . $inputDir ) . ")\n";
+            };
+        }
+    elsif (lc(NaturalDocs::Settings->InputDirectoryNameOf($inputDirs->[0])) != 1)
+        {
+        print MENUFILEHANDLE
+        "\n\n##### Do not change or remove this line. #####\n"
+        . 'Data: 2(' . NaturalDocs::ConfigFile->Obscure( NaturalDocs::Settings->InputDirectoryNameOf($inputDirs->[0]) ) . ")\n";
+        }
+
+    close(MENUFILEHANDLE);
+    };
+
+
+#
+#   Function: WriteMenuEntries
+#
+#   A recursive function to write the contents of an arrayref of <NaturalDocs::Menu::Entry> objects to disk.
+#
+#   Parameters:
+#
+#       entries          - The arrayref of menu entries to write.
+#       fileHandle      - The handle to the output file.
+#       indentChars   - The indentation _characters_ to add before each line.  It is not the number of characters, it is the characters
+#                              themselves.  Use undef for none.
+#       relativeFiles - Whether to use relative file names.
+#
+sub WriteMenuEntries #(entries, fileHandle, indentChars, relativeFiles)
+    {
+    my ($self, $entries, $fileHandle, $indentChars, $relativeFiles) = @_;
+    my $lastEntryType;
+
+    foreach my $entry (@$entries)
+        {
+        if ($entry->Type() == ::MENU_FILE())
+            {
+            my $fileName;
+
+            if ($relativeFiles)
+                {  $fileName = (NaturalDocs::Settings->SplitFromInputDirectory($entry->Target()))[1];  }
+            else
+                {  $fileName = $entry->Target();  };
+
+            print $fileHandle $indentChars . 'File: ' . $self->ConvertAmpChars( $entry->Title(), CONVERT_PARENTHESIS() )
+                                  . '  (' . ($entry->Flags() & ::MENU_FILE_NOAUTOTITLE() ? 'no auto-title, ' : '')
+                                  . $self->ConvertAmpChars($fileName) . ")\n";
+            }
+        elsif ($entry->Type() == ::MENU_GROUP())
+            {
+            if (defined $lastEntryType && $lastEntryType != ::MENU_GROUP())
+                {  print $fileHandle "\n";  };
+
+            print $fileHandle $indentChars . 'Group: ' . $self->ConvertAmpChars( $entry->Title() ) . "  {\n\n";
+            $self->WriteMenuEntries($entry->GroupContent(), $fileHandle, '   ' . $indentChars, $relativeFiles);
+            print $fileHandle '   ' . $indentChars . '}  # Group: ' . $self->ConvertAmpChars( $entry->Title() ) . "\n\n";
+            }
+        elsif ($entry->Type() == ::MENU_TEXT())
+            {
+            print $fileHandle $indentChars . 'Text: ' . $self->ConvertAmpChars( $entry->Title() ) . "\n";
+            }
+        elsif ($entry->Type() == ::MENU_LINK())
+            {
+            print $fileHandle $indentChars . 'Link: ' . $self->ConvertAmpChars( $entry->Title() ) . '  '
+                                                        . '(' . $self->ConvertAmpChars( $entry->Target(), CONVERT_PARENTHESIS() ) . ')' . "\n";
+            }
+        elsif ($entry->Type() == ::MENU_INDEX())
+            {
+            my $type;
+            if ($entry->Target() ne ::TOPIC_GENERAL())
+                {
+                $type = NaturalDocs::Topics->NameOfType($entry->Target()) . ' ';
+                };
+
+            print $fileHandle $indentChars . $self->ConvertAmpChars($type, CONVERT_COLONS()) . 'Index: '
+                                                        . $self->ConvertAmpChars( $entry->Title() ) . "\n";
+            };
+
+        $lastEntryType = $entry->Type();
+        };
+    };
+
+
+#
+#   Function: LoadPreviousMenuStateFile
+#
+#   Loads and parses the previous menu state file.
+#
+#   Returns:
+#
+#       The array ( previousMenu, previousIndexes, previousFiles ) or an empty array if there was a problem with the file.
+#
+#       previousMenu - A <MENU_GROUP> <NaturalDocs::Menu::Entry> object, similar to <menu>, which contains the entire
+#                              previous menu.
+#       previousIndexes - An existence hashref of the index <TopicTypes> present in the previous menu.
+#       previousFiles - A hashref of the files present in the previous menu.  The keys are the <FileNames>, and the entries are
+#                             references to its object in previousMenu.
+#
+sub LoadPreviousMenuStateFile
+    {
+    my ($self) = @_;
+
+    my $fileIsOkay;
+    my $version;
+    my $previousStateFileName = NaturalDocs::Project->DataFile('PreviousMenuState.nd');
+
+    if (open(PREVIOUSSTATEFILEHANDLE, '<' . $previousStateFileName))
+        {
+        # See if it's binary.
+        binmode(PREVIOUSSTATEFILEHANDLE);
+
+        my $firstChar;
+        read(PREVIOUSSTATEFILEHANDLE, $firstChar, 1);
+
+        if ($firstChar == ::BINARY_FORMAT())
+            {
+            $version = NaturalDocs::Version->FromBinaryFile(\*PREVIOUSSTATEFILEHANDLE);
+
+            # Only the topic type format has changed since switching to binary, and we support both methods.
+
+            if (NaturalDocs::Version->CheckFileFormat($version))
+                {  $fileIsOkay = 1;  }
+            else
+                {  close(PREVIOUSSTATEFILEHANDLE);  };
+            }
+
+        else # it's not in binary
+            {  close(PREVIOUSSTATEFILEHANDLE);  };
+        };
+
+    if ($fileIsOkay)
+        {
+        if (NaturalDocs::Project->UserConfigFileStatus('Menu.txt') == ::FILE_CHANGED())
+            {  $hasChanged = 1;  };
+
+
+        my $menu = NaturalDocs::Menu::Entry->New(::MENU_GROUP(), undef, undef, undef);
+        my $indexes = { };
+        my $files = { };
+
+        my @groupStack;
+        my $currentGroup = $menu;
+        my $raw;
+
+        # [UInt8: type or 0 for end group]
+
+        while (read(PREVIOUSSTATEFILEHANDLE, $raw, 1))
+            {
+            my ($type, $flags, $title, $titleLength, $target, $targetLength);
+            $type = unpack('C', $raw);
+
+            if ($type == 0)
+                {  $currentGroup = pop @groupStack;  }
+
+            elsif ($type == ::MENU_FILE())
+                {
+                # [UInt8: noAutoTitle] [AString16: title] [AString16: target]
+
+                read(PREVIOUSSTATEFILEHANDLE, $raw, 3);
+                (my $noAutoTitle, $titleLength) = unpack('Cn', $raw);
+
+                if ($noAutoTitle)
+                    {  $flags = ::MENU_FILE_NOAUTOTITLE();  };
+
+                read(PREVIOUSSTATEFILEHANDLE, $title, $titleLength);
+                read(PREVIOUSSTATEFILEHANDLE, $raw, 2);
+
+                $targetLength = unpack('n', $raw);
+
+                read(PREVIOUSSTATEFILEHANDLE, $target, $targetLength);
+                }
+
+            elsif ($type == ::MENU_GROUP())
+                {
+                # [AString16: title]
+
+                read(PREVIOUSSTATEFILEHANDLE, $raw, 2);
+                $titleLength = unpack('n', $raw);
+
+                read(PREVIOUSSTATEFILEHANDLE, $title, $titleLength);
+                }
+
+            elsif ($type == ::MENU_INDEX())
+                {
+                # [AString16: title]
+
+                read(PREVIOUSSTATEFILEHANDLE, $raw, 2);
+                $titleLength = unpack('n', $raw);
+
+                read(PREVIOUSSTATEFILEHANDLE, $title, $titleLength);
+
+                if ($version >= NaturalDocs::Version->FromString('1.3'))
+                    {
+                    # [AString16: topic type]
+                    read(PREVIOUSSTATEFILEHANDLE, $raw, 2);
+                    $targetLength = unpack('n', $raw);
+
+                    read(PREVIOUSSTATEFILEHANDLE, $target, $targetLength);
+                    }
+                else
+                    {
+                    # [UInt8: topic type (0 for general)]
+                    read(PREVIOUSSTATEFILEHANDLE, $raw, 1);
+                    $target = unpack('C', $raw);
+
+                    $target = NaturalDocs::Topics->TypeFromLegacy($target);
+                    };
+                }
+
+            elsif ($type == ::MENU_LINK())
+                {
+                # [AString16: title] [AString16: url]
+
+                read(PREVIOUSSTATEFILEHANDLE, $raw, 2);
+                $titleLength = unpack('n', $raw);
+
+                read(PREVIOUSSTATEFILEHANDLE, $title, $titleLength);
+                read(PREVIOUSSTATEFILEHANDLE, $raw, 2);
+                $targetLength = unpack('n', $raw);
+
+                read(PREVIOUSSTATEFILEHANDLE, $target, $targetLength);
+                }
+
+            elsif ($type == ::MENU_TEXT())
+                {
+                # [AString16: text]
+
+                read(PREVIOUSSTATEFILEHANDLE, $raw, 2);
+                $titleLength = unpack('n', $raw);
+
+                read(PREVIOUSSTATEFILEHANDLE, $title, $titleLength);
+                };
+
+
+            # The topic type of the index may have been removed.
+
+            if ( !($type == ::MENU_INDEX() && !NaturalDocs::Topics->IsValidType($target)) )
+                {
+                my $entry = NaturalDocs::Menu::Entry->New($type, $title, $target, ($flags || 0));
+                $currentGroup->PushToGroup($entry);
+
+                if ($type == ::MENU_FILE())
+                    {
+                    $files->{$target} = $entry;
+                    }
+                elsif ($type == ::MENU_GROUP())
+                    {
+                    push @groupStack, $currentGroup;
+                    $currentGroup = $entry;
+                    }
+                elsif ($type == ::MENU_INDEX())
+                    {
+                    $indexes->{$target} = 1;
+                    };
+                };
+
+            };
+
+        close(PREVIOUSSTATEFILEHANDLE);
+
+        return ($menu, $indexes, $files);
+        }
+    else
+        {
+        $hasChanged = 1;
+        return ( );
+        };
+    };
+
+
+#
+#   Function: SavePreviousMenuStateFile
+#
+#   Saves changes to <PreviousMenuState.nd>.
+#
+sub SavePreviousMenuStateFile
+    {
+    my ($self) = @_;
+
+    open (PREVIOUSSTATEFILEHANDLE, '>' . NaturalDocs::Project->DataFile('PreviousMenuState.nd'))
+        or die "Couldn't save " . NaturalDocs::Project->DataFile('PreviousMenuState.nd') . ".\n";
+
+    binmode(PREVIOUSSTATEFILEHANDLE);
+
+    print PREVIOUSSTATEFILEHANDLE '' . ::BINARY_FORMAT();
+
+    NaturalDocs::Version->ToBinaryFile(\*PREVIOUSSTATEFILEHANDLE, NaturalDocs::Settings->AppVersion());
+
+    $self->WritePreviousMenuStateEntries($menu->GroupContent(), \*PREVIOUSSTATEFILEHANDLE);
+
+    close(PREVIOUSSTATEFILEHANDLE);
+    };
+
+
+#
+#   Function: WritePreviousMenuStateEntries
+#
+#   A recursive function to write the contents of an arrayref of <NaturalDocs::Menu::Entry> objects to disk.
+#
+#   Parameters:
+#
+#       entries          - The arrayref of menu entries to write.
+#       fileHandle      - The handle to the output file.
+#
+sub WritePreviousMenuStateEntries #(entries, fileHandle)
+    {
+    my ($self, $entries, $fileHandle) = @_;
+
+    foreach my $entry (@$entries)
+        {
+        if ($entry->Type() == ::MENU_FILE())
+            {
+            # We need to do length manually instead of using n/A in the template because it's not supported in earlier versions
+            # of Perl.
+
+            # [UInt8: MENU_FILE] [UInt8: noAutoTitle] [AString16: title] [AString16: target]
+            print $fileHandle pack('CCnA*nA*', ::MENU_FILE(), ($entry->Flags() & ::MENU_FILE_NOAUTOTITLE() ? 1 : 0),
+                                                                length($entry->Title()), $entry->Title(),
+                                                                length($entry->Target()), $entry->Target());
+            }
+
+        elsif ($entry->Type() == ::MENU_GROUP())
+            {
+            # [UInt8: MENU_GROUP] [AString16: title]
+            print $fileHandle pack('CnA*', ::MENU_GROUP(), length($entry->Title()), $entry->Title());
+            $self->WritePreviousMenuStateEntries($entry->GroupContent(), $fileHandle);
+            print $fileHandle pack('C', 0);
+            }
+
+        elsif ($entry->Type() == ::MENU_INDEX())
+            {
+            # [UInt8: MENU_INDEX] [AString16: title] [AString16: topic type]
+            print $fileHandle pack('CnA*nA*', ::MENU_INDEX(), length($entry->Title()), $entry->Title(),
+                                                                                       length($entry->Target()), $entry->Target());
+            }
+
+        elsif ($entry->Type() == ::MENU_LINK())
+            {
+            # [UInt8: MENU_LINK] [AString16: title] [AString16: url]
+            print $fileHandle pack('CnA*nA*', ::MENU_LINK(), length($entry->Title()), $entry->Title(),
+                                                             length($entry->Target()), $entry->Target());
+            }
+
+        elsif ($entry->Type() == ::MENU_TEXT())
+            {
+            # [UInt8: MENU_TEXT] [AString16: hext]
+            print $fileHandle pack('CnA*', ::MENU_TEXT(), length($entry->Title()), $entry->Title());
+            };
+        };
+
+    };
+
+
+#
+#   Function: CheckForTrashedMenu
+#
+#   Checks the menu to see if a significant number of file entries didn't resolve to actual files, and if so, saves a backup of the
+#   menu and issues a warning.
+#
+#   Parameters:
+#
+#       numberOriginallyInMenu - A count of how many file entries were in the menu orignally.
+#       numberRemoved - A count of how many file entries were removed from the menu.
+#
+sub CheckForTrashedMenu #(numberOriginallyInMenu, numberRemoved)
+    {
+    my ($self, $numberOriginallyInMenu, $numberRemoved) = @_;
+
+    no integer;
+
+    if ( ($numberOriginallyInMenu >= 6 && $numberRemoved == $numberOriginallyInMenu) ||
+         ($numberOriginallyInMenu >= 12 && ($numberRemoved / $numberOriginallyInMenu) >= 0.4) ||
+         ($numberRemoved >= 15) )
+        {
+        my $backupFile = NaturalDocs::Project->UserConfigFile('Menu_Backup.txt');
+        my $backupFileNumber = 1;
+
+        while (-e $backupFile)
+            {
+            $backupFileNumber++;
+            $backupFile = NaturalDocs::Project->UserConfigFile('Menu_Backup_' . $backupFileNumber . '.txt');
+            };
+
+        NaturalDocs::File->Copy( NaturalDocs::Project->UserConfigFile('Menu.txt'), $backupFile );
+
+        print STDERR
+        "\n"
+        # GNU format.  See http://www.gnu.org/prep/standards_15.html
+        . "NaturalDocs: warning: possible trashed menu\n"
+        . "\n"
+        . "   Natural Docs has detected that a significant number file entries in the\n"
+        . "   menu did not resolve to actual files.  A backup of your original menu file\n"
+        . "   has been saved as\n"
+        . "\n"
+        . "   " . $backupFile . "\n"
+        . "\n"
+        . "   - If you recently deleted a lot of files from your project, you can safely\n"
+        . "     ignore this message.  They have been deleted from the menu as well.\n"
+        . "   - If you recently rearranged your source tree, you may want to restore your\n"
+        . "     menu from the backup and do a search and replace to preserve your layout.\n"
+        . "     Otherwise the position of any moved files will be reset.\n"
+        . "   - If neither of these is the case, you may have gotten the -i parameter\n"
+        . "     wrong in the command line.  You should definitely restore the backup and\n"
+        . "     try again, because otherwise every file in your menu will be reset.\n"
+        . "\n";
+        };
+
+    use integer;
+    };
+
+
+#
+#   Function: GenerateTimestampText
+#
+#   Generates <timestampText> from <timestampCode> with the current date.
+#
+sub GenerateTimestampText
+    {
+    my $self = shift;
+
+    my @longMonths = ( 'January', 'February', 'March', 'April', 'May', 'June',
+                                   'July', 'August', 'September', 'October', 'November', 'December' );
+    my @shortMonths = ( 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sept', 'Oct', 'Nov', 'Dec' );
+
+    my (undef, undef, undef, $day, $month, $year) = localtime();
+    $year += 1900;
+
+    my $longDay;
+    if ($day % 10 == 1 && $day != 11)
+        {  $longDay = $day . 'st';  }
+    elsif ($day % 10 == 2 && $day != 12)
+        {  $longDay = $day . 'nd';  }
+    elsif ($day % 10 == 3 && $day != 13)
+        {  $longDay = $day . 'rd';  }
+    else
+        {  $longDay = $day . 'th';  };
+
+
+    $timestampText = $timestampCode;
+
+    $timestampText =~ s/(?<![a-z])month(?![a-z])/$longMonths[$month]/i;
+    $timestampText =~ s/(?<![a-z])mon(?![a-z])/$shortMonths[$month]/i;
+    $timestampText =~ s/(?<![a-z])mm(?![a-z])/sprintf('%02d', $month + 1)/ie;
+    $timestampText =~ s/(?<![a-z])m(?![a-z])/$month + 1/ie;
+
+    $timestampText =~ s/(?<![a-z])day(?![a-z])/$longDay/i;
+    $timestampText =~ s/(?<![a-z])dd(?![a-z])/sprintf('%02d', $day)/ie;
+    $timestampText =~ s/(?<![a-z])d(?![a-z])/$day/i;
+
+    $timestampText =~ s/(?<![a-z])(?:year|yyyy)(?![a-z])/$year/i;
+    $timestampText =~ s/(?<![a-z])(?:year|yyyy)(?![a-z])/$year/i; #XXX
+    $timestampText =~ s/(?<![a-z])yy(?![a-z])/sprintf('%02d', $year % 100)/ie;
+    };
+
+
+use constant CONVERT_PARENTHESIS => 0x01;
+use constant CONVERT_COMMAS => 0x02;
+use constant CONVERT_COLONS => 0x04;
+
+#
+#   Function: ConvertAmpChars
+#   Replaces certain characters in the string with their entities and returns it.
+#
+#   Parameters:
+#
+#       text - The text to convert.
+#       flags - The flags of any additional characters to convert.
+#
+#   Flags:
+#
+#       - CONVERT_PARENTHESIS
+#       - CONVERT_COMMAS
+#       - CONVERT_COLONS
+#
+#   Returns:
+#
+#       The string with the amp chars converted.
+#
+sub ConvertAmpChars #(string text, int flags) => string
+    {
+    my ($self, $text, $flags) = @_;
+
+    $text =~ s/&/&amp;/g;
+    $text =~ s/\{/&lbrace;/g;
+    $text =~ s/\}/&rbrace;/g;
+
+    if ($flags & CONVERT_PARENTHESIS())
+        {
+        $text =~ s/\(/&lparen;/g;
+        $text =~ s/\)/&rparen;/g;
+        };
+    if ($flags & CONVERT_COMMAS())
+        {
+        $text =~ s/\,/&comma;/g;
+        };
+    if ($flags & CONVERT_COLONS())
+        {
+        $text =~ s/\:/&colon;/g;
+        };
+
+    return $text;
+    };
+
+
+#
+#   Function: RestoreAmpChars
+#   Replaces entity characters in the string with their original characters and returns it.  This will restore all amp chars regardless
+#   of the flags passed to <ConvertAmpChars()>.
+#
+sub RestoreAmpChars #(string text) => string
+    {
+    my ($self, $text) = @_;
+
+    $text =~ s/&lparen;/(/gi;
+    $text =~ s/&rparen;/)/gi;
+    $text =~ s/&lbrace;/{/gi;
+    $text =~ s/&rbrace;/}/gi;
+    $text =~ s/&comma;/,/gi;
+    $text =~ s/&amp;/&/gi;
+    $text =~ s/&colon;/:/gi;
+
+    return $text;
+    };
+
+
+
+###############################################################################
+# Group: Auto-Adjustment Functions
+
+
+#
+#   Function: ResolveInputDirectories
+#
+#   Detects if the input directories in the menu file match those in the command line, and if not, tries to resolve them.  This allows
+#   menu files to work across machines, since the absolute paths won't be the same but the relative ones should be.
+#
+#   Parameters:
+#
+#       inputDirectoryNames - A hashref of the input directories appearing in the menu file, or undef if none.  The keys are the
+#                                        directories, and the values are their names.  May be undef.
+#
+sub ResolveInputDirectories #(inputDirectoryNames)
+    {
+    my ($self, $menuDirectoryNames) = @_;
+
+
+    # Determine which directories don't match the command line, if any.
+
+    my $inputDirectories = NaturalDocs::Settings->InputDirectories();
+    my @unresolvedMenuDirectories;
+
+    foreach my $menuDirectory (keys %$menuDirectoryNames)
+        {
+        my $found;
+
+        foreach my $inputDirectory (@$inputDirectories)
+            {
+            if ($menuDirectory eq $inputDirectory)
+                {
+                $found = 1;
+                last;
+                };
+            };
+
+        if (!$found)
+            {  push @unresolvedMenuDirectories, $menuDirectory;  };
+        };
+
+    # Quit if everything matches up, which should be the most common case.
+    if (!scalar @unresolvedMenuDirectories)
+        {  return;  };
+
+    # Poop.  See which input directories are still available.
+
+    my @unresolvedInputDirectories;
+
+    foreach my $inputDirectory (@$inputDirectories)
+        {
+        if (!exists $menuDirectoryNames->{$inputDirectory})
+            {  push @unresolvedInputDirectories, $inputDirectory;  };
+        };
+
+    # Quit if there are none.  This means an input directory is in the menu that isn't in the command line.  Natural Docs should
+    # proceed normally and let the files be deleted.
+    if (!scalar @unresolvedInputDirectories)
+        {
+        $hasChanged = 1;
+        return;
+        };
+
+    # The index into menuDirectoryScores is the same as in unresolvedMenuDirectories.  The index into each arrayref within it is
+    # the same as in unresolvedInputDirectories.
+    my @menuDirectoryScores;
+    for (my $i = 0; $i < scalar @unresolvedMenuDirectories; $i++)
+        {  push @menuDirectoryScores, [ ];  };
+
+
+    # Now plow through the menu, looking for files that have an unresolved base.
+
+    my @menuGroups = ( $menu );
+
+    while (scalar @menuGroups)
+        {
+        my $currentGroup = pop @menuGroups;
+        my $currentGroupContent = $currentGroup->GroupContent();
+
+        foreach my $entry (@$currentGroupContent)
+            {
+            if ($entry->Type() == ::MENU_GROUP())
+                {
+                push @menuGroups, $entry;
+                }
+            elsif ($entry->Type() == ::MENU_FILE())
+                {
+                # Check if it uses an unresolved base.
+                for (my $i = 0; $i < scalar @unresolvedMenuDirectories; $i++)
+                    {
+                    if (NaturalDocs::File->IsSubPathOf($unresolvedMenuDirectories[$i], $entry->Target()))
+                        {
+                        my $relativePath = NaturalDocs::File->MakeRelativePath($unresolvedMenuDirectories[$i], $entry->Target());
+                        $self->ResolveFile($relativePath, \@unresolvedInputDirectories, $menuDirectoryScores[$i]);
+                        last;
+                        };
+                    };
+                };
+            };
+        };
+
+
+    # Now, create an array of score objects.  Each score object is the three value arrayref [ from, to, score ].  From and To are the
+    # conversion options and are the indexes into unresolvedInput/MenuDirectories.  We'll sort this array by score to get the best
+    # possible conversions.  Yes, really.
+    my @scores;
+
+    for (my $menuIndex = 0; $menuIndex < scalar @unresolvedMenuDirectories; $menuIndex++)
+        {
+        for (my $inputIndex = 0; $inputIndex < scalar @unresolvedInputDirectories; $inputIndex++)
+            {
+            if ($menuDirectoryScores[$menuIndex]->[$inputIndex])
+                {
+                push @scores, [ $menuIndex, $inputIndex, $menuDirectoryScores[$menuIndex]->[$inputIndex] ];
+                };
+            };
+        };
+
+    @scores = sort { $b->[2] <=> $a->[2] } @scores;
+
+
+    # Now we determine what goes where.
+    my @menuDirectoryConversions;
+
+    foreach my $scoreObject (@scores)
+        {
+        if (!defined $menuDirectoryConversions[ $scoreObject->[0] ])
+            {
+            $menuDirectoryConversions[ $scoreObject->[0] ] = $unresolvedInputDirectories[ $scoreObject->[1] ];
+            };
+        };
+
+
+    # Now, FINALLY, we do the conversion.  Note that not every menu directory may have a conversion defined.
+
+    @menuGroups = ( $menu );
+
+    while (scalar @menuGroups)
+        {
+        my $currentGroup = pop @menuGroups;
+        my $currentGroupContent = $currentGroup->GroupContent();
+
+        foreach my $entry (@$currentGroupContent)
+            {
+            if ($entry->Type() == ::MENU_GROUP())
+                {
+                push @menuGroups, $entry;
+                }
+            elsif ($entry->Type() == ::MENU_FILE())
+                {
+                # Check if it uses an unresolved base.
+                for (my $i = 0; $i < scalar @unresolvedMenuDirectories; $i++)
+                    {
+                    if (NaturalDocs::File->IsSubPathOf($unresolvedMenuDirectories[$i], $entry->Target()) &&
+                        defined $menuDirectoryConversions[$i])
+                        {
+                        my $relativePath = NaturalDocs::File->MakeRelativePath($unresolvedMenuDirectories[$i], $entry->Target());
+                        $entry->SetTarget( NaturalDocs::File->JoinPaths($menuDirectoryConversions[$i], $relativePath) );
+                        last;
+                        };
+                    };
+                };
+            };
+        };
+
+
+    # Whew.
+
+    $hasChanged = 1;
+    };
+
+
+#
+#   Function: ResolveRelativeInputDirectories
+#
+#   Resolves relative input directories to the input directories available.
+#
+sub ResolveRelativeInputDirectories
+    {
+    my ($self) = @_;
+
+    my $inputDirectories = NaturalDocs::Settings->InputDirectories();
+    my $resolvedInputDirectory;
+
+    if (scalar @$inputDirectories == 1)
+        {  $resolvedInputDirectory = $inputDirectories->[0];  }
+    else
+        {
+        my @score;
+
+        # Plow through the menu, looking for files and scoring them.
+
+        my @menuGroups = ( $menu );
+
+        while (scalar @menuGroups)
+            {
+            my $currentGroup = pop @menuGroups;
+            my $currentGroupContent = $currentGroup->GroupContent();
+
+            foreach my $entry (@$currentGroupContent)
+                {
+                if ($entry->Type() == ::MENU_GROUP())
+                    {
+                    push @menuGroups, $entry;
+                    }
+                elsif ($entry->Type() == ::MENU_FILE())
+                    {
+                    $self->ResolveFile($entry->Target(), $inputDirectories, \@score);
+                    };
+                };
+            };
+
+        # Determine the best match.
+
+        my $bestScore = 0;
+        my $bestIndex = 0;
+
+        for (my $i = 0; $i < scalar @$inputDirectories; $i++)
+            {
+            if ($score[$i] > $bestScore)
+                {
+                $bestScore = $score[$i];
+                $bestIndex = $i;
+                };
+            };
+
+        $resolvedInputDirectory = $inputDirectories->[$bestIndex];
+        };
+
+
+    # Okay, now that we have our resolved directory, update everything.
+
+    my @menuGroups = ( $menu );
+
+    while (scalar @menuGroups)
+        {
+        my $currentGroup = pop @menuGroups;
+        my $currentGroupContent = $currentGroup->GroupContent();
+
+        foreach my $entry (@$currentGroupContent)
+            {
+            if ($entry->Type() == ::MENU_GROUP())
+                {  push @menuGroups, $entry;  }
+            elsif ($entry->Type() == ::MENU_FILE())
+                {
+                $entry->SetTarget( NaturalDocs::File->JoinPaths($resolvedInputDirectory, $entry->Target()) );
+                };
+            };
+        };
+
+    if (scalar @$inputDirectories > 1)
+        {  $hasChanged = 1;  };
+
+    return $resolvedInputDirectory;
+    };
+
+
+#
+#   Function: ResolveFile
+#
+#   Tests a relative path against a list of directories.  Adds one to the score of each base where there is a match.
+#
+#   Parameters:
+#
+#       relativePath - The relative file name to test.
+#       possibleBases - An arrayref of bases to test it against.
+#       possibleBaseScores - An arrayref of scores to adjust.  The score indexes should correspond to the base indexes.
+#
+sub ResolveFile #(relativePath, possibleBases, possibleBaseScores)
+    {
+    my ($self, $relativePath, $possibleBases, $possibleBaseScores) = @_;
+
+    for (my $i = 0; $i < scalar @$possibleBases; $i++)
+        {
+        if (-e NaturalDocs::File->JoinPaths($possibleBases->[$i], $relativePath))
+            {  $possibleBaseScores->[$i]++;  };
+        };
+    };
+
+
+#
+#   Function: LockUserTitleChanges
+#
+#   Detects if the user manually changed any file titles, and if so, automatically locks them with <MENU_FILE_NOAUTOTITLE>.
+#
+#   Parameters:
+#
+#       previousMenuFiles - A hashref of the files from the previous menu state.  The keys are the <FileNames>, and the values are
+#                                    references to their <NaturalDocs::Menu::Entry> objects.
+#
+sub LockUserTitleChanges #(previousMenuFiles)
+    {
+    my ($self, $previousMenuFiles) = @_;
+
+    my @groupStack = ( $menu );
+    my $groupEntry;
+
+    while (scalar @groupStack)
+        {
+        $groupEntry = pop @groupStack;
+
+        foreach my $entry (@{$groupEntry->GroupContent()})
+            {
+
+            # If it's an unlocked file entry
+            if ($entry->Type() == ::MENU_FILE() && ($entry->Flags() & ::MENU_FILE_NOAUTOTITLE()) == 0)
+                {
+                my $previousEntry = $previousMenuFiles->{$entry->Target()};
+
+                # If the previous entry was also unlocked and the titles are different, the user changed the title.  Automatically lock it.
+                if (defined $previousEntry && ($previousEntry->Flags() & ::MENU_FILE_NOAUTOTITLE()) == 0 &&
+                    $entry->Title() ne $previousEntry->Title())
+                    {
+                    $entry->SetFlags($entry->Flags() | ::MENU_FILE_NOAUTOTITLE());
+                    $hasChanged = 1;
+                    };
+                }
+
+            elsif ($entry->Type() == ::MENU_GROUP())
+                {
+                push @groupStack, $entry;
+                };
+
+            };
+        };
+    };
+
+
+#
+#   Function: FlagAutoTitleChanges
+#
+#   Finds which files have auto-titles that changed and flags their groups for updating with <MENU_GROUP_UPDATETITLES> and
+#   <MENU_GROUP_UPDATEORDER>.
+#
+sub FlagAutoTitleChanges
+    {
+    my ($self) = @_;
+
+    my @groupStack = ( $menu );
+    my $groupEntry;
+
+    while (scalar @groupStack)
+        {
+        $groupEntry = pop @groupStack;
+
+        foreach my $entry (@{$groupEntry->GroupContent()})
+            {
+            if ($entry->Type() == ::MENU_FILE() && ($entry->Flags() & ::MENU_FILE_NOAUTOTITLE()) == 0 &&
+                exists $defaultTitlesChanged{$entry->Target()})
+                {
+                $groupEntry->SetFlags($groupEntry->Flags() | ::MENU_GROUP_UPDATETITLES() | ::MENU_GROUP_UPDATEORDER());
+                $hasChanged = 1;
+                }
+            elsif ($entry->Type() == ::MENU_GROUP())
+                {
+                push @groupStack, $entry;
+                };
+            };
+        };
+    };
+
+
+#
+#   Function: AutoPlaceNewFiles
+#
+#   Adds files to the menu that aren't already on it, attempting to guess where they belong.
+#
+#   New files are placed after a dummy <MENU_ENDOFORIGINAL> entry so that they don't affect the detected order.  Also, the
+#   groups they're placed in get <MENU_GROUP_UPDATETITLES>, <MENU_GROUP_UPDATESTRUCTURE>, and
+#   <MENU_GROUP_UPDATEORDER> flags.
+#
+#   Parameters:
+#
+#       filesInMenu - An existence hash of all the <FileNames> present in the menu.
+#
+sub AutoPlaceNewFiles #(fileInMenu)
+    {
+    my ($self, $filesInMenu) = @_;
+
+    my $files = NaturalDocs::Project->FilesWithContent();
+
+    my $directories;
+
+    foreach my $file (keys %$files)
+        {
+        if (!exists $filesInMenu->{$file})
+            {
+            # This is done on demand because new files shouldn't be added very often, so this will save time.
+            if (!defined $directories)
+                {  $directories = $self->MatchDirectoriesAndGroups();  };
+
+            my $targetGroup;
+            my $fileDirectoryString = (NaturalDocs::File->SplitPath($file))[1];
+
+            $targetGroup = $directories->{$fileDirectoryString};
+
+            if (!defined $targetGroup)
+                {
+                # Okay, if there's no exact match, work our way down.
+
+                my @fileDirectories = NaturalDocs::File->SplitDirectories($fileDirectoryString);
+
+                do
+                    {
+                    pop @fileDirectories;
+                    $targetGroup = $directories->{ NaturalDocs::File->JoinDirectories(@fileDirectories) };
+                    }
+                while (!defined $targetGroup && scalar @fileDirectories);
+
+                if (!defined $targetGroup)
+                    {  $targetGroup = $menu;  };
+                };
+
+            $targetGroup->MarkEndOfOriginal();
+            $targetGroup->PushToGroup( NaturalDocs::Menu::Entry->New(::MENU_FILE(), undef, $file, undef) );
+
+            $targetGroup->SetFlags( $targetGroup->Flags() | ::MENU_GROUP_UPDATETITLES() |
+                                                 ::MENU_GROUP_UPDATESTRUCTURE() | ::MENU_GROUP_UPDATEORDER() );
+
+            $hasChanged = 1;
+            };
+        };
+    };
+
+
+#
+#   Function: MatchDirectoriesAndGroups
+#
+#   Determines which groups files in certain directories should be placed in.
+#
+#   Returns:
+#
+#       A hashref.  The keys are the directory names, and the values are references to the group objects they should be placed in.
+#
+#       This only repreesents directories that currently have files on the menu, so it shouldn't be assumed that every possible
+#       directory will exist.  To match, you should first try to match the directory, and then strip the deepest directories one by
+#       one until there's a match or there's none left.  If there's none left, use the root group <menu>.
+#
+sub MatchDirectoriesAndGroups
+    {
+    my ($self) = @_;
+
+    # The keys are the directory names, and the values are hashrefs.  For the hashrefs, the keys are the group objects, and the
+    # values are the number of files in them from that directory.  In other words,
+    # $directories{$directory}->{$groupEntry} = $count;
+    my %directories;
+    # Note that we need to use Tie::RefHash to use references as keys.  Won't work otherwise.  Also, not every Perl distro comes
+    # with Tie::RefHash::Nestable, so we can't rely on that.
+
+    # We're using an index instead of pushing and popping because we want to save a list of the groups in the order they appear
+    # to break ties.
+    my @groups = ( $menu );
+    my $groupIndex = 0;
+
+
+    # Count the number of files in each group that appear in each directory.
+
+    while ($groupIndex < scalar @groups)
+        {
+        my $groupEntry = $groups[$groupIndex];
+
+        foreach my $entry (@{$groupEntry->GroupContent()})
+            {
+            if ($entry->Type() == ::MENU_GROUP())
+                {
+                push @groups, $entry;
+                }
+            elsif ($entry->Type() == ::MENU_FILE())
+                {
+                my $directory = (NaturalDocs::File->SplitPath($entry->Target()))[1];
+
+                if (!exists $directories{$directory})
+                    {
+                    my $subHash = { };
+                    tie %$subHash, 'Tie::RefHash';
+                    $directories{$directory} = $subHash;
+                    };
+
+                if (!exists $directories{$directory}->{$groupEntry})
+                    {  $directories{$directory}->{$groupEntry} = 1;  }
+                else
+                    {  $directories{$directory}->{$groupEntry}++;  };
+                };
+            };
+
+        $groupIndex++;
+        };
+
+
+    # Determine which group goes with which directory, breaking ties by using whichever group appears first.
+
+    my $finalDirectories = { };
+
+    while (my ($directory, $directoryGroups) = each %directories)
+        {
+        my $bestGroup;
+        my $bestCount = 0;
+        my %tiedGroups;  # Existence hash
+
+        while (my ($group, $count) = each %$directoryGroups)
+            {
+            if ($count > $bestCount)
+                {
+                $bestGroup = $group;
+                $bestCount = $count;
+                %tiedGroups = ( );
+                }
+            elsif ($count == $bestCount)
+                {
+                $tiedGroups{$group} = 1;
+                };
+            };
+
+        # Break ties.
+        if (scalar keys %tiedGroups)
+            {
+            $tiedGroups{$bestGroup} = 1;
+
+            foreach my $group (@groups)
+                {
+                if (exists $tiedGroups{$group})
+                    {
+                    $bestGroup = $group;
+                    last;
+                    };
+                };
+            };
+
+
+        $finalDirectories->{$directory} = $bestGroup;
+        };
+
+
+    return $finalDirectories;
+    };
+
+
+#
+#   Function: RemoveDeadFiles
+#
+#   Removes files from the menu that no longer exist or no longer have Natural Docs content.
+#
+#   Returns:
+#
+#       The number of file entries removed.
+#
+sub RemoveDeadFiles
+    {
+    my ($self) = @_;
+
+    my @groupStack = ( $menu );
+    my $numberRemoved = 0;
+
+    my $filesWithContent = NaturalDocs::Project->FilesWithContent();
+
+    while (scalar @groupStack)
+        {
+        my $groupEntry = pop @groupStack;
+        my $groupContent = $groupEntry->GroupContent();
+
+        my $index = 0;
+        while ($index < scalar @$groupContent)
+            {
+            if ($groupContent->[$index]->Type() == ::MENU_FILE() &&
+                !exists $filesWithContent->{ $groupContent->[$index]->Target() } )
+                {
+                $groupEntry->DeleteFromGroup($index);
+
+                $groupEntry->SetFlags( $groupEntry->Flags() | ::MENU_GROUP_UPDATETITLES() |
+                                                   ::MENU_GROUP_UPDATESTRUCTURE() );
+                $numberRemoved++;
+                $hasChanged = 1;
+                }
+
+            elsif ($groupContent->[$index]->Type() == ::MENU_GROUP())
+                {
+                push @groupStack, $groupContent->[$index];
+                $index++;
+                }
+
+            else
+                {  $index++;  };
+            };
+        };
+
+    return $numberRemoved;
+    };
+
+
+#
+#   Function: BanAndUnbanIndexes
+#
+#   Adjusts the indexes that are banned depending on if the user added or deleted any.
+#
+sub BanAndUnbanIndexes
+    {
+    my ($self) = @_;
+
+    # Unban any indexes that are present, meaning the user added them back manually without deleting the ban.
+    foreach my $index (keys %indexes)
+        {  delete $bannedIndexes{$index};  };
+
+    # Ban any indexes that were in the previous menu but not the current, meaning the user manually deleted them.  However,
+    # don't do this if the topic isn't indexable, meaning they changed the topic type rather than the menu.
+    foreach my $index (keys %previousIndexes)
+        {
+        if (!exists $indexes{$index} && NaturalDocs::Topics->TypeInfo($index)->Index())
+            {  $bannedIndexes{$index} = 1;  };
+        };
+    };
+
+
+#
+#   Function: AddAndRemoveIndexes
+#
+#   Automatically adds and removes index entries on the menu as necessary.  <DetectIndexGroups()> should be called
+#   beforehand.
+#
+sub AddAndRemoveIndexes
+    {
+    my ($self) = @_;
+
+    my %validIndexes;
+    my @allIndexes = NaturalDocs::Topics->AllIndexableTypes();
+
+    foreach my $index (@allIndexes)
+        {
+        # Strip the banned indexes first so it's potentially less work for SymbolTable.
+        if (!exists $bannedIndexes{$index})
+            {  $validIndexes{$index} = 1;  };
+        };
+
+    %validIndexes = %{NaturalDocs::SymbolTable->HasIndexes(\%validIndexes)};
+
+
+    # Delete dead indexes and find the best index group.
+
+    my @groupStack = ( $menu );
+
+    my $bestIndexGroup;
+    my $bestIndexCount = 0;
+
+    while (scalar @groupStack)
+        {
+        my $currentGroup = pop @groupStack;
+        my $index = 0;
+
+        my $currentIndexCount = 0;
+
+        while ($index < scalar @{$currentGroup->GroupContent()})
+            {
+            my $entry = $currentGroup->GroupContent()->[$index];
+
+            if ($entry->Type() == ::MENU_INDEX())
+                {
+                $currentIndexCount++;
+
+                if ($currentIndexCount > $bestIndexCount)
+                    {
+                    $bestIndexCount = $currentIndexCount;
+                    $bestIndexGroup = $currentGroup;
+                    };
+
+                # Remove it if it's dead.
+
+                if (!exists $validIndexes{ $entry->Target() })
+                    {
+                    $currentGroup->DeleteFromGroup($index);
+                    delete $indexes{ $entry->Target() };
+                    $hasChanged = 1;
+                    }
+                else
+                    {  $index++;  };
+                }
+
+            else
+                {
+                if ($entry->Type() == ::MENU_GROUP())
+                    {  push @groupStack, $entry;  };
+
+                $index++;
+                };
+            };
+        };
+
+
+    # Now add the new indexes.
+
+    foreach my $index (keys %indexes)
+        {  delete $validIndexes{$index};  };
+
+    if (scalar keys %validIndexes)
+        {
+        # Add a group if there are no indexes at all.
+
+        if ($bestIndexCount == 0)
+            {
+            $menu->MarkEndOfOriginal();
+
+            my $newIndexGroup = NaturalDocs::Menu::Entry->New(::MENU_GROUP(), 'Index', undef,
+                                                                                              ::MENU_GROUP_ISINDEXGROUP());
+            $menu->PushToGroup($newIndexGroup);
+
+            $bestIndexGroup = $newIndexGroup;
+            $menu->SetFlags( $menu->Flags() | ::MENU_GROUP_UPDATEORDER() | ::MENU_GROUP_UPDATESTRUCTURE() );
+            };
+
+        # Add the new indexes.
+
+        $bestIndexGroup->MarkEndOfOriginal();
+        my $isIndexGroup = $bestIndexGroup->Flags() & ::MENU_GROUP_ISINDEXGROUP();
+
+        foreach my $index (keys %validIndexes)
+            {
+            my $title;
+
+            if ($isIndexGroup)
+                {
+                if ($index eq ::TOPIC_GENERAL())
+                    {  $title = 'Everything';  }
+                else
+                    {  $title = NaturalDocs::Topics->NameOfType($index, 1);  };
+                }
+            else
+                {
+                $title = NaturalDocs::Topics->NameOfType($index) . ' Index';
+                };
+
+            my $newEntry = NaturalDocs::Menu::Entry->New(::MENU_INDEX(), $title, $index, undef);
+            $bestIndexGroup->PushToGroup($newEntry);
+
+            $indexes{$index} = 1;
+            };
+
+        $bestIndexGroup->SetFlags( $bestIndexGroup->Flags() |
+                                                   ::MENU_GROUP_UPDATEORDER() | ::MENU_GROUP_UPDATESTRUCTURE() );
+        $hasChanged = 1;
+        };
+    };
+
+
+#
+#   Function: RemoveDeadGroups
+#
+#   Removes groups with less than two entries.  It will always remove empty groups, and it will remove groups with one entry if it
+#   has the <MENU_GROUP_UPDATESTRUCTURE> flag.
+#
+sub RemoveDeadGroups
+    {
+    my ($self) = @_;
+
+    my $index = 0;
+
+    while ($index < scalar @{$menu->GroupContent()})
+        {
+        my $entry = $menu->GroupContent()->[$index];
+
+        if ($entry->Type() == ::MENU_GROUP())
+            {
+            my $removed = $self->RemoveIfDead($entry, $menu, $index);
+
+            if (!$removed)
+                {  $index++;  };
+            }
+        else
+            {  $index++;  };
+        };
+    };
+
+
+#
+#   Function: RemoveIfDead
+#
+#   Checks a group and all its sub-groups for life and remove any that are dead.  Empty groups are removed, and groups with one
+#   entry and the <MENU_GROUP_UPDATESTRUCTURE> flag have their entry moved to the parent group.
+#
+#   Parameters:
+#
+#       groupEntry - The group to check for possible deletion.
+#       parentGroupEntry - The parent group to move the single entry to if necessary.
+#       parentGroupIndex - The index of the group in its parent.
+#
+#   Returns:
+#
+#       Whether the group was removed or not.
+#
+sub RemoveIfDead #(groupEntry, parentGroupEntry, parentGroupIndex)
+    {
+    my ($self, $groupEntry, $parentGroupEntry, $parentGroupIndex) = @_;
+
+
+    # Do all sub-groups first, since their deletions will affect our UPDATESTRUCTURE flag and content count.
+
+    my $index = 0;
+    while ($index < scalar @{$groupEntry->GroupContent()})
+        {
+        my $entry = $groupEntry->GroupContent()->[$index];
+
+        if ($entry->Type() == ::MENU_GROUP())
+            {
+            my $removed = $self->RemoveIfDead($entry, $groupEntry, $index);
+
+            if (!$removed)
+                {  $index++;  };
+            }
+        else
+            {  $index++;  };
+        };
+
+
+    # Now check ourself.
+
+    my $count = scalar @{$groupEntry->GroupContent()};
+    if ($groupEntry->Flags() & ::MENU_GROUP_HASENDOFORIGINAL())
+        {  $count--;  };
+
+    if ($count == 0)
+        {
+        $parentGroupEntry->DeleteFromGroup($parentGroupIndex);
+
+        $parentGroupEntry->SetFlags( $parentGroupEntry->Flags() | ::MENU_GROUP_UPDATESTRUCTURE() );
+
+        $hasChanged = 1;
+        return 1;
+        }
+    elsif ($count == 1 && ($groupEntry->Flags() & ::MENU_GROUP_UPDATESTRUCTURE()) )
+        {
+        my $onlyEntry = $groupEntry->GroupContent()->[0];
+        if ($onlyEntry->Type() == ::MENU_ENDOFORIGINAL())
+            {  $onlyEntry = $groupEntry->GroupContent()->[1];  };
+
+        $parentGroupEntry->DeleteFromGroup($parentGroupIndex);
+
+        $parentGroupEntry->MarkEndOfOriginal();
+        $parentGroupEntry->PushToGroup($onlyEntry);
+
+        $parentGroupEntry->SetFlags( $parentGroupEntry->Flags() | ::MENU_GROUP_UPDATETITLES() |
+                                                     ::MENU_GROUP_UPDATEORDER() | ::MENU_GROUP_UPDATESTRUCTURE() );
+
+        $hasChanged = 1;
+        return 1;
+        }
+    else
+        {  return undef;  };
+    };
+
+
+#
+#   Function: DetectIndexGroups
+#
+#   Finds groups that are primarily used for indexes and gives them the <MENU_GROUP_ISINDEXGROUP> flag.
+#
+sub DetectIndexGroups
+    {
+    my ($self) = @_;
+
+    my @groupStack = ( $menu );
+
+    while (scalar @groupStack)
+        {
+        my $groupEntry = pop @groupStack;
+
+        my $isIndexGroup = -1;  # -1: Can't tell yet.  0: Can't be an index group.  1: Is an index group so far.
+
+        foreach my $entry (@{$groupEntry->GroupContent()})
+            {
+            if ($entry->Type() == ::MENU_INDEX())
+                {
+                if ($isIndexGroup == -1)
+                    {  $isIndexGroup = 1;  };
+                }
+
+            # Text is tolerated, but it still needs at least one index entry.
+            elsif ($entry->Type() != ::MENU_TEXT())
+                {
+                $isIndexGroup = 0;
+
+                if ($entry->Type() == ::MENU_GROUP())
+                    {  push @groupStack, $entry;  };
+                };
+            };
+
+        if ($isIndexGroup == 1)
+            {
+            $groupEntry->SetFlags( $groupEntry->Flags() | ::MENU_GROUP_ISINDEXGROUP() );
+            };
+        };
+    };
+
+
+#
+#   Function: CreateDirectorySubGroups
+#
+#   Where possible, creates sub-groups based on directories for any long groups that have <MENU_GROUP_UPDATESTRUCTURE>
+#   set.  Clears the flag afterwards on groups that are short enough to not need any more sub-groups, but leaves it for the rest.
+#
+sub CreateDirectorySubGroups
+    {
+    my ($self) = @_;
+
+    my @groupStack = ( $menu );
+
+    foreach my $groupEntry (@groupStack)
+        {
+        if ($groupEntry->Flags() & ::MENU_GROUP_UPDATESTRUCTURE())
+            {
+            # Count the number of files.
+
+            my $fileCount = 0;
+
+            foreach my $entry (@{$groupEntry->GroupContent()})
+                {
+                if ($entry->Type() == ::MENU_FILE())
+                    {  $fileCount++;  };
+                };
+
+
+            if ($fileCount > MAXFILESINGROUP)
+                {
+                my @sharedDirectories = $self->SharedDirectoriesOf($groupEntry);
+                my $unsharedIndex = scalar @sharedDirectories;
+
+                # The keys are the first directory entries after the shared ones, and the values are the number of files that are in
+                # that directory.  Files that don't have subdirectories after the shared directories aren't included because they shouldn't
+                # be put in a subgroup.
+                my %directoryCounts;
+
+                foreach my $entry (@{$groupEntry->GroupContent()})
+                    {
+                    if ($entry->Type() == ::MENU_FILE())
+                        {
+                        my @entryDirectories = NaturalDocs::File->SplitDirectories( (NaturalDocs::File->SplitPath($entry->Target()))[1] );
+
+                        if (scalar @entryDirectories > $unsharedIndex)
+                            {
+                            my $unsharedDirectory = $entryDirectories[$unsharedIndex];
+
+                            if (!exists $directoryCounts{$unsharedDirectory})
+                                {  $directoryCounts{$unsharedDirectory} = 1;  }
+                            else
+                                {  $directoryCounts{$unsharedDirectory}++;  };
+                            };
+                        };
+                    };
+
+
+                # Now create the subgroups.
+
+                # The keys are the first directory entries after the shared ones, and the values are the groups for those files to be
+                # put in.  There will only be entries for the groups with at least MINFILESINNEWGROUP files.
+                my %directoryGroups;
+
+                while (my ($directory, $count) = each %directoryCounts)
+                    {
+                    if ($count >= MINFILESINNEWGROUP)
+                        {
+                        my $newGroup = NaturalDocs::Menu::Entry->New( ::MENU_GROUP(), ucfirst($directory), undef,
+                                                                                                   ::MENU_GROUP_UPDATETITLES() |
+                                                                                                   ::MENU_GROUP_UPDATEORDER() );
+
+                        if ($count > MAXFILESINGROUP)
+                            {  $newGroup->SetFlags( $newGroup->Flags() | ::MENU_GROUP_UPDATESTRUCTURE());  };
+
+                        $groupEntry->MarkEndOfOriginal();
+                        push @{$groupEntry->GroupContent()}, $newGroup;
+
+                        $directoryGroups{$directory} = $newGroup;
+                        $fileCount -= $count;
+                        };
+                    };
+
+
+                # Now fill the subgroups.
+
+                if (scalar keys %directoryGroups)
+                    {
+                    my $afterOriginal;
+                    my $index = 0;
+
+                    while ($index < scalar @{$groupEntry->GroupContent()})
+                        {
+                        my $entry = $groupEntry->GroupContent()->[$index];
+
+                        if ($entry->Type() == ::MENU_FILE())
+                            {
+                            my @entryDirectories =
+                                NaturalDocs::File->SplitDirectories( (NaturalDocs::File->SplitPath($entry->Target()))[1] );
+
+                            my $unsharedDirectory = $entryDirectories[$unsharedIndex];
+
+                            if (exists $directoryGroups{$unsharedDirectory})
+                                {
+                                my $targetGroup = $directoryGroups{$unsharedDirectory};
+
+                                if ($afterOriginal)
+                                    {  $targetGroup->MarkEndOfOriginal();  };
+                                $targetGroup->PushToGroup($entry);
+
+                                $groupEntry->DeleteFromGroup($index);
+                                }
+                            else
+                                {  $index++;  };
+                            }
+
+                        elsif ($entry->Type() == ::MENU_ENDOFORIGINAL())
+                            {
+                            $afterOriginal = 1;
+                            $index++;
+                            }
+
+                        elsif ($entry->Type() == ::MENU_GROUP())
+                            {
+                            # See if we need to relocate this group.
+
+                            my @groupDirectories = $self->SharedDirectoriesOf($entry);
+
+                            # The group's shared directories must be at least two levels deeper than the current.  If the first level deeper
+                            # is a new group, move it there because it's a subdirectory of that one.
+                            if (scalar @groupDirectories - scalar @sharedDirectories >= 2)
+                                {
+                                my $unsharedDirectory = $groupDirectories[$unsharedIndex];
+
+                                if (exists $directoryGroups{$unsharedDirectory} &&
+                                    $directoryGroups{$unsharedDirectory} != $entry)
+                                    {
+                                    my $targetGroup = $directoryGroups{$unsharedDirectory};
+
+                                    if ($afterOriginal)
+                                        {  $targetGroup->MarkEndOfOriginal();  };
+                                    $targetGroup->PushToGroup($entry);
+
+                                    $groupEntry->DeleteFromGroup($index);
+
+                                    # We need to retitle the group if it has the name of the unshared directory.
+
+                                    my $oldTitle = $entry->Title();
+                                    $oldTitle =~ s/ +//g;
+                                    $unsharedDirectory =~ s/ +//g;
+
+                                    if (lc($oldTitle) eq lc($unsharedDirectory))
+                                        {
+                                        $entry->SetTitle($groupDirectories[$unsharedIndex + 1]);
+                                        };
+                                    }
+                                else
+                                    {  $index++;  };
+                                }
+                            else
+                                {  $index++;  };
+                            }
+
+                        else
+                            {  $index++;  };
+                        };
+
+                    $hasChanged = 1;
+
+                    if ($fileCount <= MAXFILESINGROUP)
+                        {  $groupEntry->SetFlags( $groupEntry->Flags() & ~::MENU_GROUP_UPDATESTRUCTURE() );  };
+
+                    $groupEntry->SetFlags( $groupEntry->Flags() | ::MENU_GROUP_UPDATETITLES() |
+                                                                                         ::MENU_GROUP_UPDATEORDER() );
+                    };
+
+                };  # If group has >MAXFILESINGROUP files
+            };  # If group has UPDATESTRUCTURE
+
+
+        # Okay, now go through all the subgroups.  We do this after the above so that newly created groups can get subgrouped
+        # further.
+
+        foreach my $entry (@{$groupEntry->GroupContent()})
+            {
+            if ($entry->Type() == ::MENU_GROUP())
+                {  push @groupStack, $entry;  };
+            };
+
+        };  # For each group entry
+    };
+
+
+#
+#   Function: DetectOrder
+#
+#   Detects the order of the entries in all groups that have the <MENU_GROUP_UPDATEORDER> flag set.  Will set one of the
+#   <MENU_GROUP_FILESSORTED>, <MENU_GROUP_FILESANDGROUPSSORTED>, <MENU_GROUP_EVERYTHINGSORTED>, or
+#   <MENU_GROUP_UNSORTED> flags.  It will always go for the most comprehensive sort possible, so if a group only has one
+#   entry, it will be flagged as <MENU_GROUP_EVERYTHINGSORTED>.
+#
+#   <DetectIndexGroups()> should be called beforehand, as the <MENU_GROUP_ISINDEXGROUP> flag affects how the order is
+#   detected.
+#
+#   The sort detection stops if it reaches a <MENU_ENDOFORIGINAL> entry, so new entries can be added to the end while still
+#   allowing the original sort to be detected.
+#
+#   Parameters:
+#
+#       forceAll - If set, the order will be detected for all groups regardless of whether <MENU_GROUP_UPDATEORDER> is set.
+#
+sub DetectOrder #(forceAll)
+    {
+    my ($self, $forceAll) = @_;
+    my @groupStack = ( $menu );
+
+    while (scalar @groupStack)
+        {
+        my $groupEntry = pop @groupStack;
+        my $index = 0;
+
+
+        # First detect the sort.
+
+        if ($forceAll || ($groupEntry->Flags() & ::MENU_GROUP_UPDATEORDER()) )
+            {
+            my $order = ::MENU_GROUP_EVERYTHINGSORTED();
+
+            my $lastFile;
+            my $lastFileOrGroup;
+
+            while ($index < scalar @{$groupEntry->GroupContent()} &&
+                     $groupEntry->GroupContent()->[$index]->Type() != ::MENU_ENDOFORIGINAL() &&
+                     $order != ::MENU_GROUP_UNSORTED())
+                {
+                my $entry = $groupEntry->GroupContent()->[$index];
+
+
+                # Ignore the last entry if it's an index group.  We don't want it to affect the sort.
+
+                if ($index + 1 == scalar @{$groupEntry->GroupContent()} &&
+                    $entry->Type() == ::MENU_GROUP() && ($entry->Flags() & ::MENU_GROUP_ISINDEXGROUP()) )
+                    {
+                    # Ignore.
+
+                    # This is an awkward code construct, basically working towards an else instead of using an if, but the code just gets
+                    # too hard to read otherwise.  The compiled code should work out to roughly the same thing anyway.
+                    }
+
+
+                # Ignore the first entry if it's the general index in an index group.  We don't want it to affect the sort.
+
+                elsif ($index == 0 && ($groupEntry->Flags() & ::MENU_GROUP_ISINDEXGROUP()) &&
+                        $entry->Type() == ::MENU_INDEX() && $entry->Target() eq ::TOPIC_GENERAL() )
+                    {
+                    # Ignore.
+                    }
+
+
+                # Degenerate the sort.
+
+                else
+                    {
+
+                    if ($order == ::MENU_GROUP_EVERYTHINGSORTED() && $index > 0 &&
+                        ::StringCompare($entry->Title(), $groupEntry->GroupContent()->[$index - 1]->Title()) < 0)
+                        {  $order = ::MENU_GROUP_FILESANDGROUPSSORTED();  };
+
+                    if ($order == ::MENU_GROUP_FILESANDGROUPSSORTED() &&
+                        ($entry->Type() == ::MENU_FILE() || $entry->Type() == ::MENU_GROUP()) &&
+                        defined $lastFileOrGroup && ::StringCompare($entry->Title(), $lastFileOrGroup->Title()) < 0)
+                        {  $order = ::MENU_GROUP_FILESSORTED();  };
+
+                    if ($order == ::MENU_GROUP_FILESSORTED() &&
+                        $entry->Type() == ::MENU_FILE() && defined $lastFile &&
+                        ::StringCompare($entry->Title(), $lastFile->Title()) < 0)
+                        {  $order = ::MENU_GROUP_UNSORTED();  };
+
+                    };
+
+
+                # Set the lastX parameters for comparison and add sub-groups to the stack.
+
+                if ($entry->Type() == ::MENU_FILE())
+                    {
+                    $lastFile = $entry;
+                    $lastFileOrGroup = $entry;
+                    }
+                elsif ($entry->Type() == ::MENU_GROUP())
+                    {
+                    $lastFileOrGroup = $entry;
+                    push @groupStack, $entry;
+                    };
+
+                $index++;
+                };
+
+            $groupEntry->SetFlags($groupEntry->Flags() | $order);
+            };
+
+
+        # Find any subgroups in the remaining entries.
+
+        while ($index < scalar @{$groupEntry->GroupContent()})
+            {
+            my $entry = $groupEntry->GroupContent()->[$index];
+
+            if ($entry->Type() == ::MENU_GROUP())
+                {  push @groupStack, $entry;  };
+
+            $index++;
+            };
+        };
+    };
+
+
+#
+#   Function: GenerateAutoFileTitles
+#
+#   Creates titles for the unlocked file entries in all groups that have the <MENU_GROUP_UPDATETITLES> flag set.  It clears the
+#   flag afterwards so it can be used efficiently for multiple sweeps.
+#
+#   Parameters:
+#
+#       forceAll - If set, forces all the unlocked file titles to update regardless of whether the group has the
+#                     <MENU_GROUP_UPDATETITLES> flag set.
+#
+sub GenerateAutoFileTitles #(forceAll)
+    {
+    my ($self, $forceAll) = @_;
+
+    my @groupStack = ( $menu );
+
+    while (scalar @groupStack)
+        {
+        my $groupEntry = pop @groupStack;
+
+        if ($forceAll || ($groupEntry->Flags() & ::MENU_GROUP_UPDATETITLES()) )
+            {
+            # Find common prefixes and paths to strip from the default menu titles.
+
+            my @sharedDirectories = $self->SharedDirectoriesOf($groupEntry);
+            my $noSharedDirectories = (scalar @sharedDirectories == 0);
+
+            my @sharedPrefixes;
+            my $noSharedPrefixes;
+
+            foreach my $entry (@{$groupEntry->GroupContent()})
+                {
+                if ($entry->Type() == ::MENU_FILE())
+                    {
+                    # Find the common prefixes among all file entries that are unlocked and don't use the file name as their default title.
+
+                    my $defaultTitle = NaturalDocs::Project->DefaultMenuTitleOf($entry->Target());
+
+                    if (!$noSharedPrefixes && ($entry->Flags() & ::MENU_FILE_NOAUTOTITLE()) == 0 &&
+                        $defaultTitle ne $entry->Target())
+                        {
+                        # If the filename is part of the title, separate it off so no part of it gets included as a common prefix.  This would
+                        # happen if there's a group with only one file in it (Project.h => h) or only files that differ by extension
+                        # (Project.h, Project.cpp => h, cpp) and people labeled them manually (// File: Project.h).
+                        my $filename = (NaturalDocs::File->SplitPath($entry->Target()))[2];
+                        my $filenamePart;
+
+                        if ( length $defaultTitle >= length $filename &&
+                             lc(substr($defaultTitle, 0 - length($filename))) eq lc($filename) )
+                            {
+                            $filenamePart = substr($defaultTitle, 0 - length($filename));
+                            $defaultTitle = substr($defaultTitle, 0, 0 - length($filename));
+                            };
+
+
+                        my @entryPrefixes = split(/(\.|::|->)/, $defaultTitle);
+
+                        # Remove potential leading undef/empty string.
+                        if (!length $entryPrefixes[0])
+                            {  shift @entryPrefixes;  };
+
+                        # Remove last entry.  Something has to exist for the title.  If we already separated off the filename, that will be
+                        # it instead.
+                        if (!$filenamePart)
+                            {  pop @entryPrefixes;  };
+
+                        if (!scalar @entryPrefixes)
+                            {  $noSharedPrefixes = 1;  }
+                        elsif (!scalar @sharedPrefixes)
+                            {  @sharedPrefixes = @entryPrefixes;  }
+                        elsif ($entryPrefixes[0] ne $sharedPrefixes[0])
+                            {  $noSharedPrefixes = 1;  }
+
+                        # If both arrays have entries, and the first is shared...
+                        else
+                            {
+                            my $index = 1;
+
+                            while ($index < scalar @sharedPrefixes && $entryPrefixes[$index] eq $sharedPrefixes[$index])
+                                {  $index++;  };
+
+                            if ($index < scalar @sharedPrefixes)
+                                {  splice(@sharedPrefixes, $index);  };
+                            };
+                        };
+
+                    };  # if entry is MENU_FILE
+                };  # foreach entry in group content.
+
+
+            if (!scalar @sharedPrefixes)
+                {  $noSharedPrefixes = 1;  };
+
+
+            # Update all the menu titles of unlocked file entries.
+
+            foreach my $entry (@{$groupEntry->GroupContent()})
+                {
+                if ($entry->Type() == ::MENU_FILE() && ($entry->Flags() & ::MENU_FILE_NOAUTOTITLE()) == 0)
+                    {
+                    my $title = NaturalDocs::Project->DefaultMenuTitleOf($entry->Target());
+
+                    if ($title eq $entry->Target())
+                        {
+                        my ($volume, $directoryString, $file) = NaturalDocs::File->SplitPath($entry->Target());
+                        my @directories = NaturalDocs::File->SplitDirectories($directoryString);
+
+                        if (!$noSharedDirectories)
+                            {  splice(@directories, 0, scalar @sharedDirectories);  };
+
+                        # directory\...\directory\file.ext
+
+                        if (scalar @directories > 2)
+                            {  @directories = ( $directories[0], '...', $directories[-1] );  };
+
+                        $directoryString = NaturalDocs::File->JoinDirectories(@directories);
+                        $title = NaturalDocs::File->JoinPaths($directoryString, $file);
+                        }
+
+                    else
+                        {
+                        my $filename = (NaturalDocs::File->SplitPath($entry->Target()))[2];
+                        my $filenamePart;
+
+                        if ( length $title >= length $filename &&
+                             lc(substr($title, 0 - length($filename))) eq lc($filename) )
+                            {
+                            $filenamePart = substr($title, 0 - length($filename));
+                            $title = substr($title, 0, 0 - length($filename));
+                            };
+
+                        my @segments = split(/(::|\.|->)/, $title);
+                        if (!length $segments[0])
+                            {  shift @segments;  };
+
+                        if ($filenamePart)
+                            {  push @segments, $filenamePart;  };
+
+                        if (!$noSharedPrefixes)
+                            {  splice(@segments, 0, scalar @sharedPrefixes);  };
+
+                        # package...package::target
+
+                        if (scalar @segments > 5)
+                            {  splice(@segments, 1, scalar @segments - 4, '...');  };
+
+                        $title = join('', @segments);
+                        };
+
+                    $entry->SetTitle($title);
+                    };  # If entry is an unlocked file
+                };  # Foreach entry
+
+            $groupEntry->SetFlags( $groupEntry->Flags() & ~::MENU_GROUP_UPDATETITLES() );
+
+            };  # If updating group titles
+
+        # Now find any subgroups.
+        foreach my $entry (@{$groupEntry->GroupContent()})
+            {
+            if ($entry->Type() == ::MENU_GROUP())
+                {  push @groupStack, $entry;  };
+            };
+        };
+
+    };
+
+
+#
+#   Function: ResortGroups
+#
+#   Resorts all groups that have <MENU_GROUP_UPDATEORDER> set.  Assumes <DetectOrder()> and <GenerateAutoFileTitles()>
+#   have already been called.  Will clear the flag and any <MENU_ENDOFORIGINAL> entries on reordered groups.
+#
+#   Parameters:
+#
+#       forceAll - If set, resorts all groups regardless of whether <MENU_GROUP_UPDATEORDER> is set.
+#
+sub ResortGroups #(forceAll)
+    {
+    my ($self, $forceAll) = @_;
+    my @groupStack = ( $menu );
+
+    while (scalar @groupStack)
+        {
+        my $groupEntry = pop @groupStack;
+
+        if ($forceAll || ($groupEntry->Flags() & ::MENU_GROUP_UPDATEORDER()) )
+            {
+            my $newEntriesIndex;
+
+
+            # Strip the ENDOFORIGINAL.
+
+            if ($groupEntry->Flags() & ::MENU_GROUP_HASENDOFORIGINAL())
+                {
+                $newEntriesIndex = 0;
+
+                while ($newEntriesIndex < scalar @{$groupEntry->GroupContent()} &&
+                         $groupEntry->GroupContent()->[$newEntriesIndex]->Type() != ::MENU_ENDOFORIGINAL() )
+                    {  $newEntriesIndex++;  };
+
+                $groupEntry->DeleteFromGroup($newEntriesIndex);
+
+                $groupEntry->SetFlags( $groupEntry->Flags() & ~::MENU_GROUP_HASENDOFORIGINAL() );
+                }
+            else
+                {  $newEntriesIndex = -1;  };
+
+
+            # Strip the exceptions.
+
+            my $trailingIndexGroup;
+            my $leadingGeneralIndex;
+
+            if ( ($groupEntry->Flags() & ::MENU_GROUP_ISINDEXGROUP()) &&
+                 $groupEntry->GroupContent()->[0]->Type() == ::MENU_INDEX() &&
+                 $groupEntry->GroupContent()->[0]->Target() eq ::TOPIC_GENERAL() )
+                {
+                $leadingGeneralIndex = shift @{$groupEntry->GroupContent()};
+                if ($newEntriesIndex != -1)
+                    {  $newEntriesIndex--;  };
+                }
+
+            elsif (scalar @{$groupEntry->GroupContent()} && $newEntriesIndex != 0)
+                {
+                my $lastIndex;
+
+                if ($newEntriesIndex != -1)
+                    {  $lastIndex = $newEntriesIndex - 1;  }
+                else
+                    {  $lastIndex = scalar @{$groupEntry->GroupContent()} - 1;  };
+
+                if ($groupEntry->GroupContent()->[$lastIndex]->Type() == ::MENU_GROUP() &&
+                    ( $groupEntry->GroupContent()->[$lastIndex]->Flags() & ::MENU_GROUP_ISINDEXGROUP() ) )
+                    {
+                    $trailingIndexGroup = $groupEntry->GroupContent()->[$lastIndex];
+                    $groupEntry->DeleteFromGroup($lastIndex);
+
+                    if ($newEntriesIndex != -1)
+                        {  $newEntriesIndex++;  };
+                    };
+                };
+
+
+            # If there weren't already exceptions, strip them from the new entries.
+
+            if ( (!defined $trailingIndexGroup || !defined $leadingGeneralIndex) && $newEntriesIndex != -1)
+                {
+                my $index = $newEntriesIndex;
+
+                while ($index < scalar @{$groupEntry->GroupContent()})
+                    {
+                    my $entry = $groupEntry->GroupContent()->[$index];
+
+                    if (!defined $trailingIndexGroup &&
+                        $entry->Type() == ::MENU_GROUP() && ($entry->Flags() & ::MENU_GROUP_ISINDEXGROUP()) )
+                        {
+                        $trailingIndexGroup = $entry;
+                        $groupEntry->DeleteFromGroup($index);
+                        }
+                    elsif (!defined $leadingGeneralIndex && ($groupEntry->Flags() & ::MENU_GROUP_ISINDEXGROUP()) &&
+                            $entry->Type() == ::MENU_INDEX() && !defined $entry->Target())
+                        {
+                        $leadingGeneralIndex = $entry;
+                        $groupEntry->DeleteFromGroup($index);
+                        }
+                    else
+                        {  $index++;  };
+                    };
+                };
+
+
+            # If there's no order, we still want to sort the new additions.
+
+            if ($groupEntry->Flags() & ::MENU_GROUP_UNSORTED())
+                {
+                if ($newEntriesIndex != -1)
+                    {
+                    my @newEntries =
+                        @{$groupEntry->GroupContent()}[$newEntriesIndex..scalar @{$groupEntry->GroupContent()} - 1];
+
+                    @newEntries = sort { $self->CompareEntries($a, $b) } @newEntries;
+
+                    foreach my $newEntry (@newEntries)
+                        {
+                        $groupEntry->GroupContent()->[$newEntriesIndex] = $newEntry;
+                        $newEntriesIndex++;
+                        };
+                    };
+                }
+
+            elsif ($groupEntry->Flags() & ::MENU_GROUP_EVERYTHINGSORTED())
+                {
+                @{$groupEntry->GroupContent()} = sort { $self->CompareEntries($a, $b) } @{$groupEntry->GroupContent()};
+                }
+
+            elsif ( ($groupEntry->Flags() & ::MENU_GROUP_FILESSORTED()) ||
+                     ($groupEntry->Flags() & ::MENU_GROUP_FILESANDGROUPSSORTED()) )
+                {
+                my $groupContent = $groupEntry->GroupContent();
+                my @newEntries;
+
+                if ($newEntriesIndex != -1)
+                    {  @newEntries = splice( @$groupContent, $newEntriesIndex );  };
+
+
+                # First resort the existing entries.
+
+                # A couple of support functions.  They're defined here instead of spun off into their own functions because they're only
+                # used here and to make them general we would need to add support for the other sort options.
+
+                sub IsIncludedInSort #(groupEntry, entry)
+                    {
+                    my ($self, $groupEntry, $entry) = @_;
+
+                    return ($entry->Type() == ::MENU_FILE() ||
+                                ( $entry->Type() == ::MENU_GROUP() &&
+                                    ($groupEntry->Flags() & ::MENU_GROUP_FILESANDGROUPSSORTED()) ) );
+                    };
+
+                sub IsSorted #(groupEntry)
+                    {
+                    my ($self, $groupEntry) = @_;
+                    my $lastApplicable;
+
+                    foreach my $entry (@{$groupEntry->GroupContent()})
+                        {
+                        # If the entry is applicable to the sort order...
+                        if ($self->IsIncludedInSort($groupEntry, $entry))
+                            {
+                            if (defined $lastApplicable)
+                                {
+                                if ($self->CompareEntries($entry, $lastApplicable) < 0)
+                                    {  return undef;  };
+                                };
+
+                            $lastApplicable = $entry;
+                            };
+                        };
+
+                    return 1;
+                    };
+
+
+                # There's a good chance it's still sorted.  They should only become unsorted if an auto-title changes.
+                if (!$self->IsSorted($groupEntry))
+                    {
+                    # Crap.  Okay, method one is to sort each group of continuous sortable elements.  There's a possibility that doing
+                    # this will cause the whole to become sorted again.  We try this first, even though it isn't guaranteed to succeed,
+                    # because it will restore the sort without moving any unsortable entries.
+
+                    # Copy it because we'll need the original if this fails.
+                    my @originalGroupContent = @$groupContent;
+
+                    my $index = 0;
+                    my $startSortable = 0;
+
+                    while (1)
+                        {
+                        # If index is on an unsortable entry or the end of the array...
+                        if ($index == scalar @$groupContent || !$self->IsIncludedInSort($groupEntry, $groupContent->[$index]))
+                            {
+                            # If we have at least two sortable entries...
+                            if ($index - $startSortable >= 2)
+                                {
+                                # Sort them.
+                                my @sortableEntries = @{$groupContent}[$startSortable .. $index - 1];
+                                @sortableEntries = sort { $self->CompareEntries($a, $b) } @sortableEntries;
+                                foreach my $sortableEntry (@sortableEntries)
+                                    {
+                                    $groupContent->[$startSortable] = $sortableEntry;
+                                    $startSortable++;
+                                    };
+                                };
+
+                            if ($index == scalar @$groupContent)
+                                {  last;  };
+
+                            $startSortable = $index + 1;
+                            };
+
+                        $index++;
+                        };
+
+                    if (!$self->IsSorted($groupEntry))
+                        {
+                        # Crap crap.  Okay, now we do a full sort but with potential damage to the original structure.  Each unsortable
+                        # element is locked to the next sortable element.  We sort the sortable elements, bringing all the unsortable
+                        # pieces with them.
+
+                        my @pieces = ( [ ] );
+                        my $currentPiece = $pieces[0];
+
+                        foreach my $entry (@originalGroupContent)
+                            {
+                            push @$currentPiece, $entry;
+
+                            # If the entry is sortable...
+                            if ($self->IsIncludedInSort($groupEntry, $entry))
+                                {
+                                $currentPiece = [ ];
+                                push @pieces, $currentPiece;
+                                };
+                            };
+
+                        my $lastUnsortablePiece;
+
+                        # If the last entry was sortable, we'll have an empty piece at the end.  Drop it.
+                        if (scalar @{$pieces[-1]} == 0)
+                            {  pop @pieces;  }
+
+                        # If the last entry wasn't sortable, the last piece won't end with a sortable element.  Save it, but remove it
+                        # from the list.
+                        else
+                            {  $lastUnsortablePiece = pop @pieces;  };
+
+                        # Sort the list.
+                        @pieces = sort { $self->CompareEntries( $a->[-1], $b->[-1] ) } @pieces;
+
+                        # Copy it back to the original.
+                        if (defined $lastUnsortablePiece)
+                            {  push @pieces, $lastUnsortablePiece;  };
+
+                        my $index = 0;
+
+                        foreach my $piece (@pieces)
+                            {
+                            foreach my $entry (@{$piece})
+                                {
+                                $groupEntry->GroupContent()->[$index] = $entry;
+                                $index++;
+                                };
+                            };
+                        };
+                    };
+
+
+                # Okay, the orginal entries are sorted now.  Sort the new entries and apply.
+
+                if (scalar @newEntries)
+                    {
+                    @newEntries = sort { $self->CompareEntries($a, $b) } @newEntries;
+                    my @originalEntries = @$groupContent;
+                    @$groupContent = ( );
+
+                    while (1)
+                        {
+                        while (scalar @originalEntries && !$self->IsIncludedInSort($groupEntry, $originalEntries[0]))
+                            {  push @$groupContent, (shift @originalEntries);  };
+
+                        if (!scalar @originalEntries || !scalar @newEntries)
+                            {  last;  };
+
+                        while (scalar @newEntries && $self->CompareEntries($newEntries[0], $originalEntries[0]) < 0)
+                            {  push @$groupContent, (shift @newEntries);  };
+
+                        push @$groupContent, (shift @originalEntries);
+
+                        if (!scalar @originalEntries || !scalar @newEntries)
+                            {  last;  };
+                        };
+
+                    if (scalar @originalEntries)
+                        {  push @$groupContent, @originalEntries;  }
+                    elsif (scalar @newEntries)
+                        {  push @$groupContent, @newEntries;  };
+                    };
+                };
+
+
+            # Now re-add the exceptions.
+
+            if (defined $leadingGeneralIndex)
+                {
+                unshift @{$groupEntry->GroupContent()}, $leadingGeneralIndex;
+                };
+
+            if (defined $trailingIndexGroup)
+                {
+                $groupEntry->PushToGroup($trailingIndexGroup);
+                };
+
+            };
+
+        foreach my $entry (@{$groupEntry->GroupContent()})
+            {
+            if ($entry->Type() == ::MENU_GROUP())
+                {  push @groupStack, $entry;  };
+            };
+        };
+    };
+
+
+#
+#   Function: CompareEntries
+#
+#   A comparison function for use in sorting.  Compares the two entries by their titles with <StringCompare()>, but in the case
+#   of a tie, puts <MENU_FILE> entries above <MENU_GROUP> entries.
+#
+sub CompareEntries #(a, b)
+    {
+    my ($self, $a, $b) = @_;
+
+    my $result = ::StringCompare($a->Title(), $b->Title());
+
+    if ($result == 0)
+        {
+        if ($a->Type() == ::MENU_FILE() && $b->Type() == ::MENU_GROUP())
+            {  $result = -1;  }
+        elsif ($a->Type() == ::MENU_GROUP() && $b->Type() == ::MENU_FILE())
+            {  $result = 1;  };
+        };
+
+    return $result;
+    };
+
+
+#
+#   Function: SharedDirectoriesOf
+#
+#   Returns an array of all the directories shared by the files in the group.  If none, returns an empty array.
+#
+sub SharedDirectoriesOf #(group)
+    {
+    my ($self, $groupEntry) = @_;
+    my @sharedDirectories;
+
+    foreach my $entry (@{$groupEntry->GroupContent()})
+        {
+        if ($entry->Type() == ::MENU_FILE())
+            {
+            my @entryDirectories = NaturalDocs::File->SplitDirectories( (NaturalDocs::File->SplitPath($entry->Target()))[1] );
+
+            if (!scalar @sharedDirectories)
+                {  @sharedDirectories = @entryDirectories;  }
+            else
+                {  ::ShortenToMatchStrings(\@sharedDirectories, \@entryDirectories);  };
+
+            if (!scalar @sharedDirectories)
+                {  last;  };
+            };
+        };
+
+    return @sharedDirectories;
+    };
+
+
+1;

Added: trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/NDMarkup.pm
===================================================================
--- trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/NDMarkup.pm	                        (rev 0)
+++ trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/NDMarkup.pm	2009-07-01 15:30:00 UTC (rev 4275)
@@ -0,0 +1,76 @@
+###############################################################################
+#
+#   Package: NaturalDocs::NDMarkup
+#
+###############################################################################
+#
+#   A package of support functions for dealing with <NDMarkup>.
+#
+#   Usage and Dependencies:
+#
+#       The package doesn't depend on any Natural Docs packages and is ready to use right away.
+#
+###############################################################################
+
+# 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::NDMarkup;
+
+#
+#   Function: ConvertAmpChars
+#
+#   Substitutes certain characters with their <NDMarkup> amp chars.
+#
+#   Parameters:
+#
+#       text - The block of text to convert.
+#
+#   Returns:
+#
+#       The converted text block.
+#
+sub ConvertAmpChars #(text)
+    {
+    my ($self, $text) = @_;
+
+    $text =~ s/&/&amp;/g;
+    $text =~ s/</&lt;/g;
+    $text =~ s/>/&gt;/g;
+    $text =~ s/\"/&quot;/g;
+
+    return $text;
+    };
+
+
+#
+#   Function: RestoreAmpChars
+#
+#   Replaces <NDMarkup> amp chars with their original symbols.
+#
+#   Parameters:
+#
+#       text - The text to restore.
+#
+#   Returns:
+#
+#       The restored text.
+#
+sub RestoreAmpChars #(text)
+    {
+    my ($self, $text) = @_;
+
+    $text =~ s/&quot;/\"/g;
+    $text =~ s/&gt;/>/g;
+    $text =~ s/&lt;/</g;
+    $text =~ s/&amp;/&/g;
+
+    return $text;
+    };
+
+
+1;

Added: trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Parser/JavaDoc.pm
===================================================================
--- trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Parser/JavaDoc.pm	                        (rev 0)
+++ trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Parser/JavaDoc.pm	2009-07-01 15:30:00 UTC (rev 4275)
@@ -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(/(&lt;\/?pre&gt;)/, $line);
+
+        foreach my $token (@tokens)
+            {
+            if ($token =~ /^&lt;pre&gt;$/i)
+                {
+                if (!$inCode && $unformattedText)
+                    {
+                    $output .= '<p>' . $self->FormatText($unformattedText, 1) . '</p>';
+                    };
+
+                $inCode = 1;
+                $unformattedText = undef;
+                }
+            elsif ($token =~ /^&lt;\/pre&gt;$/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/&lt;b&gt;(.*?)&lt;\/b&gt;/<b>$1<\/b>/gi;
+    $text =~ s/&lt;i&gt;(.*?)&lt;\/i&gt;/<i>$1<\/i>/gi;
+    $text =~ s/&lt;u&gt;(.*?)&lt;\/u&gt;/<u>$1<\/u>/gi;
+
+    $text =~ s/&lt;code&gt;(.*?)&lt;\/code&gt;/$1/gi;
+
+    $text =~ s/&lt;ul.*?&gt;(.*?)&lt;\/ul&gt;/<ul>$1<\/ul>/gi;
+    $text =~ s/&lt;ol.*?&gt;(.*?)&lt;\/ol&gt;/<ul>$1<\/ul>/gi;
+    $text =~ s/&lt;li.*?&gt;(.*?)&lt;\/li&gt;/<li>$1<\/li>/gi;
+
+    $text =~ s/&lt;!--.*?--&gt;//gi;
+
+    $text =~ s/&lt;\/p&gt;//gi;
+    $text =~ s/^&lt;p&gt;//i;
+    if ($inParagraph)
+        {  $text =~ s/&lt;p&gt;/<\/p><p>/gi;  }
+    else
+        {  $text =~ s/&lt;p&gt;//gi;  };
+
+    $text =~ s/&lt;a href=&quot;mailto:(.*?)&quot;.*?&gt;(.*?)&lt;\/a&gt;/$self->MakeEMailLink($1, $2)/gie;
+    $text =~ s/&lt;a href=&quot;(.*?)&quot;.*?&gt;(.*?)&lt;\/a&gt;/$self->MakeURLLink($1, $2)/gie;
+
+    $text =~ s/&amp;nbsp;/ /gi;
+    $text =~ s/&amp;amp;/&amp;/gi;
+    $text =~ s/&amp;gt;/&gt;/gi;
+    $text =~ s/&amp;lt;/&lt;/gi;
+    $text =~ s/&amp;quot;/&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/&/&amp;/g;
+    $text =~ s/</&lt;/g;
+    $text =~ s/>/&gt;/g;
+    $text =~ s/"/&quot;/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;

Added: trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Parser/Native.pm
===================================================================
--- trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Parser/Native.pm	                        (rev 0)
+++ trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Parser/Native.pm	2009-07-01 15:30:00 UTC (rev 4275)
@@ -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="&lt;' . $linkText . '&gt;">';  };
+                }
+
+            else # it's not a link.
+                {
+                $output .= '&lt;';
+                };
+            }
+
+        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;

Added: trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Parser/ParsedTopic.pm
===================================================================
--- trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Parser/ParsedTopic.pm	                        (rev 0)
+++ trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Parser/ParsedTopic.pm	2009-07-01 15:30:00 UTC (rev 4275)
@@ -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;

Added: trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Parser.pm
===================================================================
--- trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Parser.pm	                        (rev 0)
+++ trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Parser.pm	2009-07-01 15:30:00 UTC (rev 4275)
@@ -0,0 +1,1331 @@
+###############################################################################
+#
+#   Package: NaturalDocs::Parser
+#
+###############################################################################
+#
+#   A package that coordinates source file parsing between the <NaturalDocs::Languages::Base>-derived objects and its own
+#   sub-packages such as <NaturalDocs::Parser::Native>.  Also handles sending symbols to <NaturalDocs::SymbolTable> and
+#   other generic topic processing.
+#
+#   Usage and Dependencies:
+#
+#       - Prior to use, <NaturalDocs::Settings>, <NaturalDocs::Languages>, <NaturalDocs::Project>, <NaturalDocs::SymbolTable>,
+#         and <NaturalDocs::ClassHierarchy> must be initialized.  <NaturalDocs::SymbolTable> and <NaturalDocs::ClassHierarchy>
+#         do not have to be fully resolved.
+#
+#       - Aside from that, the package is ready to use right away.  It does not have its own initialization function.
+#
+###############################################################################
+
+# This file is part of Natural Docs, which is Copyright (C) 2003-2008 Greg Valure
+# Natural Docs is licensed under the GPL
+
+use NaturalDocs::Parser::ParsedTopic;
+use NaturalDocs::Parser::Native;
+use NaturalDocs::Parser::JavaDoc;
+
+use strict;
+use integer;
+
+package NaturalDocs::Parser;
+
+
+
+###############################################################################
+# Group: Variables
+
+
+#
+#   var: sourceFile
+#
+#   The source <FileName> currently being parsed.
+#
+my $sourceFile;
+
+#
+#   var: language
+#
+#   The language object for the file, derived from <NaturalDocs::Languages::Base>.
+#
+my $language;
+
+#
+#   Array: parsedFile
+#
+#   An array of <NaturalDocs::Parser::ParsedTopic> objects.
+#
+my @parsedFile;
+
+
+#
+#   bool: parsingForInformation
+#   Whether <ParseForInformation()> was called.  If false, then <ParseForBuild()> was called.
+#
+my $parsingForInformation;
+
+
+
+###############################################################################
+# Group: Functions
+
+#
+#   Function: ParseForInformation
+#
+#   Parses the input file for information.  Will update the information about the file in <NaturalDocs::SymbolTable> and
+#   <NaturalDocs::Project>.
+#
+#   Parameters:
+#
+#       file - The <FileName> to parse.
+#
+sub ParseForInformation #(file)
+    {
+    my ($self, $file) = @_;
+    $sourceFile = $file;
+
+    $parsingForInformation = 1;
+
+    # Watch this parse so we detect any changes.
+    NaturalDocs::SymbolTable->WatchFileForChanges($sourceFile);
+    NaturalDocs::ClassHierarchy->WatchFileForChanges($sourceFile);
+    NaturalDocs::SourceDB->WatchFileForChanges($sourceFile);
+
+    my $defaultMenuTitle = $self->Parse();
+
+    foreach my $topic (@parsedFile)
+        {
+        # Add a symbol for the topic.
+
+        my $type = $topic->Type();
+        if ($type eq ::TOPIC_ENUMERATION())
+            {  $type = ::TOPIC_TYPE();  };
+
+        NaturalDocs::SymbolTable->AddSymbol($topic->Symbol(), $sourceFile, $type,
+                                                                   $topic->Prototype(), $topic->Summary());
+
+
+        # You can't put the function call directly in a while with a regex.  It has to sit in a variable to work.
+        my $body = $topic->Body();
+
+
+        # If it's a list or enum topic, add a symbol for each description list entry.
+
+        if ($topic->IsList() || $topic->Type() eq ::TOPIC_ENUMERATION())
+            {
+            # We'll hijack the enum constants to apply to non-enum behavior too.
+            my $behavior;
+
+            if ($topic->Type() eq ::TOPIC_ENUMERATION())
+                {
+                $type = ::TOPIC_CONSTANT();
+                $behavior = $language->EnumValues();
+                }
+            elsif (NaturalDocs::Topics->TypeInfo($topic->Type())->Scope() == ::SCOPE_ALWAYS_GLOBAL())
+                {
+                $behavior = ::ENUM_GLOBAL();
+                }
+            else
+                {
+                $behavior = ::ENUM_UNDER_PARENT();
+                };
+
+            while ($body =~ /<ds>([^<]+)<\/ds><dd>(.*?)<\/dd>/g)
+                {
+                my ($listTextSymbol, $listSummary) = ($1, $2);
+
+                $listTextSymbol = NaturalDocs::NDMarkup->RestoreAmpChars($listTextSymbol);
+                my $listSymbol = NaturalDocs::SymbolString->FromText($listTextSymbol);
+
+                if ($behavior == ::ENUM_UNDER_PARENT())
+                    {  $listSymbol = NaturalDocs::SymbolString->Join($topic->Package(), $listSymbol);  }
+                elsif ($behavior == ::ENUM_UNDER_TYPE())
+                    {  $listSymbol = NaturalDocs::SymbolString->Join($topic->Symbol(), $listSymbol);  };
+
+                NaturalDocs::SymbolTable->AddSymbol($listSymbol, $sourceFile, $type, undef,
+                                                                           $self->GetSummaryFromDescriptionList($listSummary));
+                };
+            };
+
+
+        # Add references in the topic.
+
+        while ($body =~ /<link target=\"([^\"]*)\" name=\"[^\"]*\" original=\"[^\"]*\">/g)
+            {
+            my $linkText = NaturalDocs::NDMarkup->RestoreAmpChars($1);
+            my $linkSymbol = NaturalDocs::SymbolString->FromText($linkText);
+
+            NaturalDocs::SymbolTable->AddReference(::REFERENCE_TEXT(), $linkSymbol,
+                                                                           $topic->Package(), $topic->Using(), $sourceFile);
+            };
+
+
+        # Add images in the topic.
+
+        while ($body =~ /<img mode=\"[^\"]*\" target=\"([^\"]+)\" original=\"[^\"]*\">/g)
+            {
+            my $target = NaturalDocs::NDMarkup->RestoreAmpChars($1);
+            NaturalDocs::ImageReferenceTable->AddReference($sourceFile, $target);
+            };
+        };
+
+    # Handle any changes to the file.
+    NaturalDocs::ClassHierarchy->AnalyzeChanges();
+    NaturalDocs::SymbolTable->AnalyzeChanges();
+    NaturalDocs::SourceDB->AnalyzeWatchedFileChanges();
+
+    # Update project on the file's characteristics.
+    my $hasContent = (scalar @parsedFile > 0);
+
+    NaturalDocs::Project->SetHasContent($sourceFile, $hasContent);
+    if ($hasContent)
+        {  NaturalDocs::Project->SetDefaultMenuTitle($sourceFile, $defaultMenuTitle);  };
+
+    # We don't need to keep this around.
+    @parsedFile = ( );
+    };
+
+
+#
+#   Function: ParseForBuild
+#
+#   Parses the input file for building, returning it as a <NaturalDocs::Parser::ParsedTopic> arrayref.
+#
+#   Note that all new and changed files should be parsed for symbols via <ParseForInformation()> before calling this function on
+#   *any* file.  The reason is that <NaturalDocs::SymbolTable> needs to know about all the symbol definitions and references to
+#   resolve them properly.
+#
+#   Parameters:
+#
+#       file - The <FileName> to parse for building.
+#
+#   Returns:
+#
+#       An arrayref of the source file as <NaturalDocs::Parser::ParsedTopic> objects.
+#
+sub ParseForBuild #(file)
+    {
+    my ($self, $file) = @_;
+    $sourceFile = $file;
+
+    $parsingForInformation = undef;
+
+    $self->Parse();
+
+    return \@parsedFile;
+    };
+
+
+
+
+###############################################################################
+# Group: Interface Functions
+
+
+#
+#   Function: OnComment
+#
+#   The function called by <NaturalDocs::Languages::Base>-derived objects when their parsers encounter a comment
+#   suitable for documentation.
+#
+#   Parameters:
+#
+#       commentLines - An arrayref of the comment's lines.  The language's comment symbols should be converted to spaces,
+#                               and there should be no line break characters at the end of each line.  *The original memory will be
+#                               changed.*
+#       lineNumber - The line number of the first of the comment lines.
+#       isJavaDoc - Whether the comment is in JavaDoc format.
+#
+#   Returns:
+#
+#       The number of topics created by this comment, or zero if none.
+#
+sub OnComment #(string[] commentLines, int lineNumber, bool isJavaDoc)
+    {
+    my ($self, $commentLines, $lineNumber, $isJavaDoc) = @_;
+
+    $self->CleanComment($commentLines);
+
+    # We check if it's definitely Natural Docs content first.  This overrides all else, since it's possible that a comment could start
+    # with a topic line yet have something that looks like a JavaDoc tag.  Natural Docs wins in this case.
+    if (NaturalDocs::Parser::Native->IsMine($commentLines, $isJavaDoc))
+        {  return NaturalDocs::Parser::Native->ParseComment($commentLines, $isJavaDoc, $lineNumber, \@parsedFile);  }
+
+    elsif (NaturalDocs::Parser::JavaDoc->IsMine($commentLines, $isJavaDoc))
+        {  return NaturalDocs::Parser::JavaDoc->ParseComment($commentLines, $isJavaDoc, $lineNumber, \@parsedFile);  }
+
+    # If the content is ambiguous and it's a JavaDoc-styled comment, treat it as Natural Docs content.
+    elsif ($isJavaDoc)
+        {  return NaturalDocs::Parser::Native->ParseComment($commentLines, $isJavaDoc, $lineNumber, \@parsedFile);  }
+    };
+
+
+#
+#   Function: OnClass
+#
+#   A function called by <NaturalDocs::Languages::Base>-derived objects when their parsers encounter a class declaration.
+#
+#   Parameters:
+#
+#       class - The <SymbolString> of the class encountered.
+#
+sub OnClass #(class)
+    {
+    my ($self, $class) = @_;
+
+    if ($parsingForInformation)
+        {  NaturalDocs::ClassHierarchy->AddClass($sourceFile, $class);  };
+    };
+
+
+#
+#   Function: OnClassParent
+#
+#   A function called by <NaturalDocs::Languages::Base>-derived objects when their parsers encounter a declaration of
+#   inheritance.
+#
+#   Parameters:
+#
+#       class - The <SymbolString> of the class we're in.
+#       parent - The <SymbolString> of the class it inherits.
+#       scope - The package <SymbolString> that the reference appeared in.
+#       using - An arrayref of package <SymbolStrings> that the reference has access to via "using" statements.
+#       resolvingFlags - Any <Resolving Flags> to be used when resolving the reference.  <RESOLVE_NOPLURAL> is added
+#                              automatically since that would never apply to source code.
+#
+sub OnClassParent #(class, parent, scope, using, resolvingFlags)
+    {
+    my ($self, $class, $parent, $scope, $using, $resolvingFlags) = @_;
+
+    if ($parsingForInformation)
+        {
+        NaturalDocs::ClassHierarchy->AddParentReference($sourceFile, $class, $parent, $scope, $using,
+                                                                                   $resolvingFlags | ::RESOLVE_NOPLURAL());
+        };
+    };
+
+
+
+###############################################################################
+# Group: Support Functions
+
+
+#   Function: Parse
+#
+#   Opens the source file and parses process.  Most of the actual parsing is done in <NaturalDocs::Languages::Base->ParseFile()>
+#   and <OnComment()>, though.
+#
+#   *Do not call externally.*  Rather, call <ParseForInformation()> or <ParseForBuild()>.
+#
+#   Returns:
+#
+#       The default menu title of the file.  Will be the <FileName> if nothing better is found.
+#
+sub Parse
+    {
+    my ($self) = @_;
+
+    NaturalDocs::Error->OnStartParsing($sourceFile);
+
+    $language = NaturalDocs::Languages->LanguageOf($sourceFile);
+    NaturalDocs::Parser::Native->Start();
+    @parsedFile = ( );
+
+    my ($autoTopics, $scopeRecord) = $language->ParseFile($sourceFile, \@parsedFile);
+
+
+    $self->AddToClassHierarchy();
+
+    $self->BreakLists();
+
+    if (defined $autoTopics)
+        {
+        if (defined $scopeRecord)
+            {  $self->RepairPackages($autoTopics, $scopeRecord);  };
+
+        $self->MergeAutoTopics($language, $autoTopics);
+        };
+
+    $self->RemoveRemainingHeaderlessTopics();
+
+
+    # We don't need to do this if there aren't any auto-topics because the only package changes would be implied by the comments.
+    if (defined $autoTopics)
+        {  $self->AddPackageDelineators();  };
+
+    if (!NaturalDocs::Settings->NoAutoGroup())
+        {  $self->MakeAutoGroups($autoTopics);  };
+
+
+    # Set the menu title.
+
+    my $defaultMenuTitle = $sourceFile;
+
+    if (scalar @parsedFile)
+        {
+        my $addFileTitle;
+
+        if (NaturalDocs::Settings->OnlyFileTitles())
+            {
+            # We still want to use the title from the topics if the first one is a file.
+            if ($parsedFile[0]->Type() eq ::TOPIC_FILE())
+                {  $addFileTitle = 0;  }
+            else
+                {  $addFileTitle = 1;  };
+            }
+        elsif (scalar @parsedFile == 1 || NaturalDocs::Topics->TypeInfo( $parsedFile[0]->Type() )->PageTitleIfFirst())
+            {  $addFileTitle = 0;  }
+        else
+            {  $addFileTitle = 1;  };
+
+        if (!$addFileTitle)
+            {
+            $defaultMenuTitle = $parsedFile[0]->Title();
+            }
+        else
+            {
+            # If the title ended up being the file name, add a leading section for it.
+
+            unshift @parsedFile,
+                       NaturalDocs::Parser::ParsedTopic->New(::TOPIC_FILE(), (NaturalDocs::File->SplitPath($sourceFile))[2],
+                                                                                  undef, undef, undef, undef, undef, 1, undef);
+            };
+        };
+
+    NaturalDocs::Error->OnEndParsing($sourceFile);
+
+    return $defaultMenuTitle;
+    };
+
+
+#
+#   Function: CleanComment
+#
+#   Removes any extraneous formatting and whitespace from the comment.  Eliminates comment boxes, horizontal lines, trailing
+#   whitespace from lines, and expands all tab characters.  It keeps leading whitespace, though, since it may be needed for
+#   example code, and blank lines, since the original line numbers are needed.
+#
+#   Parameters:
+#
+#       commentLines  - An arrayref of the comment lines to clean.  *The original memory will be changed.*  Lines should have the
+#                                language's comment symbols replaced by spaces and not have a trailing line break.
+#
+sub CleanComment #(commentLines)
+    {
+    my ($self, $commentLines) = @_;
+
+    use constant DONT_KNOW => 0;
+    use constant IS_UNIFORM => 1;
+    use constant IS_UNIFORM_IF_AT_END => 2;
+    use constant IS_NOT_UNIFORM => 3;
+
+    my $leftSide = DONT_KNOW;
+    my $rightSide = DONT_KNOW;
+    my $leftSideChar;
+    my $rightSideChar;
+
+    my $index = 0;
+    my $tabLength = NaturalDocs::Settings->TabLength();
+
+    while ($index < scalar @$commentLines)
+        {
+        # Strip trailing whitespace from the original.
+
+        $commentLines->[$index] =~ s/[ \t]+$//;
+
+
+        # Expand tabs in the original.  This method is almost six times faster than Text::Tabs' method.
+
+        my $tabIndex = index($commentLines->[$index], "\t");
+
+        while ($tabIndex != -1)
+            {
+            substr( $commentLines->[$index], $tabIndex, 1, ' ' x ($tabLength - ($tabIndex % $tabLength)) );
+            $tabIndex = index($commentLines->[$index], "\t", $tabIndex);
+            };
+
+
+        # Make a working copy and strip leading whitespace as well.  This has to be done after tabs are expanded because
+        # stripping indentation could change how far tabs are expanded.
+
+        my $line = $commentLines->[$index];
+        $line =~ s/^ +//;
+
+        # If the line is blank...
+        if (!length $line)
+            {
+            # If we have a potential vertical line, this only acceptable if it's at the end of the comment.
+            if ($leftSide == IS_UNIFORM)
+                {  $leftSide = IS_UNIFORM_IF_AT_END;  };
+            if ($rightSide == IS_UNIFORM)
+                {  $rightSide = IS_UNIFORM_IF_AT_END;  };
+            }
+
+        # If there's at least four symbols in a row, it's a horizontal line.  The second regex supports differing edge characters.  It
+        # doesn't matter if any of this matches the left and right side symbols.  The length < 256 is a sanity check, because that
+        # regexp has caused the perl regexp engine to choke on an insane line someone sent me from an automatically generated
+        # file.  It had over 10k characters on the first line, and most of them were 0x00.
+        elsif ($line =~ /^([^a-zA-Z0-9 ])\1{3,}$/ ||
+                (length $line < 256 && $line =~ /^([^a-zA-Z0-9 ])\1*([^a-zA-Z0-9 ])\2{3,}([^a-zA-Z0-9 ])\3*$/) )
+            {
+            # Ignore it.  This has no effect on the vertical line detection.  We want to keep it in the output though in case it was
+            # in a code section.
+            }
+
+        # If the line is not blank or a horizontal line...
+        else
+            {
+            # More content means any previous blank lines are no longer tolerated in vertical line detection.  They are only
+            # acceptable at the end of the comment.
+
+            if ($leftSide == IS_UNIFORM_IF_AT_END)
+                {  $leftSide = IS_NOT_UNIFORM;  };
+            if ($rightSide == IS_UNIFORM_IF_AT_END)
+                {  $rightSide = IS_NOT_UNIFORM;  };
+
+
+            # Detect vertical lines.  Lines are only lines if they are followed by whitespace or a connected horizontal line.
+            # Otherwise we may accidentally detect lines from short comments that just happen to have every first or last
+            # character the same.
+
+            if ($leftSide != IS_NOT_UNIFORM)
+                {
+                if ($line =~ /^([^a-zA-Z0-9])\1*(?: |$)/)
+                    {
+                    if ($leftSide == DONT_KNOW)
+                        {
+                        $leftSide = IS_UNIFORM;
+                        $leftSideChar = $1;
+                        }
+                    else # ($leftSide == IS_UNIFORM)  Other choices already ruled out.
+                        {
+                        if ($leftSideChar ne $1)
+                            {  $leftSide = IS_NOT_UNIFORM;  };
+                        };
+                    }
+                # We'll tolerate the lack of symbols on the left on the first line, because it may be a
+                # /* Function: Whatever
+                #  * Description.
+                #  */
+                # comment which would have the leading /* blanked out.
+                elsif ($index != 0)
+                    {
+                    $leftSide = IS_NOT_UNIFORM;
+                    };
+                };
+
+            if ($rightSide != IS_NOT_UNIFORM)
+                {
+                if ($line =~ / ([^a-zA-Z0-9])\1*$/)
+                    {
+                    if ($rightSide == DONT_KNOW)
+                        {
+                        $rightSide = IS_UNIFORM;
+                        $rightSideChar = $1;
+                        }
+                    else # ($rightSide == IS_UNIFORM)  Other choices already ruled out.
+                        {
+                        if ($rightSideChar ne $1)
+                            {  $rightSide = IS_NOT_UNIFORM;  };
+                        };
+                    }
+                else
+                    {
+                    $rightSide = IS_NOT_UNIFORM;
+                    };
+                };
+
+            # We'll remove vertical lines later if they're uniform throughout the entire comment.
+            };
+
+        $index++;
+        };
+
+
+    if ($leftSide == IS_UNIFORM_IF_AT_END)
+        {  $leftSide = IS_UNIFORM;  };
+    if ($rightSide == IS_UNIFORM_IF_AT_END)
+        {  $rightSide = IS_UNIFORM;  };
+
+
+    $index = 0;
+    my $inCodeSection = 0;
+
+    while ($index < scalar @$commentLines)
+        {
+        # Clear horizontal lines only if we're not in a code section.
+        if ($commentLines->[$index] =~ /^ *([^a-zA-Z0-9 ])\1{3,}$/ ||
+            ( length $commentLines->[$index] < 256 &&
+              $commentLines->[$index] =~ /^ *([^a-zA-Z0-9 ])\1*([^a-zA-Z0-9 ])\2{3,}([^a-zA-Z0-9 ])\3*$/ ) )
+        	{
+        	if (!$inCodeSection)
+        		{  $commentLines->[$index] = '';  }
+        	}
+
+        else
+        	{
+	        # Clear vertical lines.
+
+	        if ($leftSide == IS_UNIFORM)
+	            {
+	            # This works because every line should either start this way, be blank, or be the first line that doesn't start with a
+	            # symbol.
+	            $commentLines->[$index] =~ s/^ *([^a-zA-Z0-9 ])\1*//;
+	            };
+
+	        if ($rightSide == IS_UNIFORM)
+	            {
+	            $commentLines->[$index] =~ s/ *([^a-zA-Z0-9 ])\1*$//;
+	            };
+
+
+	        # Clear horizontal lines again if there were vertical lines.  This catches lines that were separated from the verticals by
+	        # whitespace.
+
+	        if (($leftSide == IS_UNIFORM || $rightSide == IS_UNIFORM) && !$inCodeSection)
+	            {
+	            $commentLines->[$index] =~ s/^ *([^a-zA-Z0-9 ])\1{3,}$//;
+	            $commentLines->[$index] =~ s/^ *([^a-zA-Z0-9 ])\1*([^a-zA-Z0-9 ])\2{3,}([^a-zA-Z0-9 ])\3*$//;
+	            };
+
+
+	        # Check for the start and end of code sections.  Note that this doesn't affect vertical line removal.
+
+	        if (!$inCodeSection &&
+	        	$commentLines->[$index] =~ /^ *\( *(?:(?:start|begin)? +)?(?:table|code|example|diagram) *\)$/i )
+	        	{
+	        	$inCodeSection = 1;
+	        	}
+	        elsif ($inCodeSection &&
+	        	    $commentLines->[$index] =~ /^ *\( *(?:end|finish|done)(?: +(?:table|code|example|diagram))? *\)$/i)
+	        	 {
+	        	 $inCodeSection = 0;
+	        	 }
+	        }
+
+
+        $index++;
+        };
+
+    };
+
+
+
+###############################################################################
+# Group: Processing Functions
+
+
+#
+#   Function: RepairPackages
+#
+#   Recalculates the packages for all comment topics using the auto-topics and the scope record.  Call this *before* calling
+#   <MergeAutoTopics()>.
+#
+#   Parameters:
+#
+#       autoTopics - A reference to the list of automatically generated <NaturalDocs::Parser::ParsedTopics>.
+#       scopeRecord - A reference to an array of <NaturalDocs::Languages::Advanced::ScopeChanges>.
+#
+sub RepairPackages #(autoTopics, scopeRecord)
+    {
+    my ($self, $autoTopics, $scopeRecord) = @_;
+
+    my $topicIndex = 0;
+    my $autoTopicIndex = 0;
+    my $scopeIndex = 0;
+
+    my $topic = $parsedFile[0];
+    my $autoTopic = $autoTopics->[0];
+    my $scopeChange = $scopeRecord->[0];
+
+    my $currentPackage;
+    my $inFakePackage;
+
+    while (defined $topic)
+        {
+        # First update the scope via the record if its defined and has the lowest line number.
+        if (defined $scopeChange &&
+            $scopeChange->LineNumber() <= $topic->LineNumber() &&
+            (!defined $autoTopic || $scopeChange->LineNumber() <= $autoTopic->LineNumber()) )
+            {
+            $currentPackage = $scopeChange->Scope();
+            $scopeIndex++;
+            $scopeChange = $scopeRecord->[$scopeIndex];  # Will be undef when past end.
+            $inFakePackage = undef;
+            }
+
+        # Next try to end a fake scope with an auto topic if its defined and has the lowest line number.
+        elsif (defined $autoTopic &&
+                $autoTopic->LineNumber() <= $topic->LineNumber())
+            {
+            if ($inFakePackage)
+                {
+                $currentPackage = $autoTopic->Package();
+                $inFakePackage = undef;
+                };
+
+            $autoTopicIndex++;
+            $autoTopic = $autoTopics->[$autoTopicIndex];  # Will be undef when past end.
+            }
+
+
+        # Finally try to handle the topic, since it has the lowest line number.  Check for Type() because headerless topics won't have
+        # one.
+        else
+            {
+            my $scope;
+            if ($topic->Type())
+                {  $scope = NaturalDocs::Topics->TypeInfo($topic->Type())->Scope();  }
+            else
+                {  $scope = ::SCOPE_NORMAL();  };
+
+            if ($scope == ::SCOPE_START() || $scope == ::SCOPE_END())
+                {
+                # They should already have the correct class and scope.
+                $currentPackage = $topic->Package();
+                $inFakePackage = 1;
+                }
+           else
+                {
+                # Fix the package of everything else.
+
+                # Note that the first function or variable topic to appear in a fake package will assume that package even if it turns out
+                # to be incorrect in the actual code, since the topic will come before the auto-topic.  This will be corrected in
+                # MergeAutoTopics().
+
+                $topic->SetPackage($currentPackage);
+                };
+
+            $topicIndex++;
+            $topic = $parsedFile[$topicIndex];  # Will be undef when past end.
+            };
+        };
+
+    };
+
+
+#
+#   Function: MergeAutoTopics
+#
+#   Merges the automatically generated topics into the file.  If an auto-topic matches an existing topic, it will have it's prototype
+#   and package transferred.  If it doesn't, the auto-topic will be inserted into the list unless
+#   <NaturalDocs::Settings->DocumentedOnly()> is set.  If an existing topic doesn't have a title, it's assumed to be a headerless
+#   comment and will be merged with the next auto-topic or discarded.
+#
+#   Parameters:
+#
+#       language - The <NaturalDocs::Languages::Base>-derived class for the file.
+#       autoTopics - A reference to the list of automatically generated topics.
+#
+sub MergeAutoTopics #(language, autoTopics)
+    {
+    my ($self, $language, $autoTopics) = @_;
+
+    my $topicIndex = 0;
+    my $autoTopicIndex = 0;
+
+    # Keys are topic types, values are existence hashrefs of titles.
+    my %topicsInLists;
+
+    while ($topicIndex < scalar @parsedFile && $autoTopicIndex < scalar @$autoTopics)
+        {
+        my $topic = $parsedFile[$topicIndex];
+        my $autoTopic = $autoTopics->[$autoTopicIndex];
+
+        my $cleanTitle = $topic->Title();
+        $cleanTitle =~ s/[\t ]*\([^\(]*$//;
+
+        # Add the auto-topic if it's higher in the file than the current topic.
+        if ($autoTopic->LineNumber() < $topic->LineNumber())
+            {
+            if (exists $topicsInLists{$autoTopic->Type()} &&
+                exists $topicsInLists{$autoTopic->Type()}->{$autoTopic->Title()})
+                {
+                # Remove it from the list so a second one with the same name will be added.
+                delete $topicsInLists{$autoTopic->Type()}->{$autoTopic->Title()};
+                }
+            elsif (!NaturalDocs::Settings->DocumentedOnly())
+                {
+                splice(@parsedFile, $topicIndex, 0, $autoTopic);
+                $topicIndex++;
+                };
+
+            $autoTopicIndex++;
+            }
+
+        # Remove a headerless topic if there's another topic between it and the next auto-topic.
+        elsif (!$topic->Title() && $topicIndex + 1 < scalar @parsedFile &&
+                $parsedFile[$topicIndex+1]->LineNumber() < $autoTopic->LineNumber())
+            {
+            splice(@parsedFile, $topicIndex, 1);
+            }
+
+        # Transfer information if we have a match or a headerless topic.
+        elsif ( !$topic->Title() || ($topic->Type() == $autoTopic->Type() && index($autoTopic->Title(), $cleanTitle) != -1) )
+            {
+            $topic->SetType($autoTopic->Type());
+            $topic->SetPrototype($autoTopic->Prototype());
+            $topic->SetUsing($autoTopic->Using());
+
+            if (!$topic->Title())
+                {  $topic->SetTitle($autoTopic->Title());  };
+
+            if (NaturalDocs::Topics->TypeInfo($topic->Type())->Scope() != ::SCOPE_START())
+                {  $topic->SetPackage($autoTopic->Package());  }
+            elsif ($autoTopic->Package() ne $topic->Package())
+                {
+                my @autoPackageIdentifiers = NaturalDocs::SymbolString->IdentifiersOf($autoTopic->Package());
+                my @packageIdentifiers = NaturalDocs::SymbolString->IdentifiersOf($topic->Package());
+
+                while (scalar @autoPackageIdentifiers && $autoPackageIdentifiers[-1] eq $packageIdentifiers[-1])
+                    {
+                    pop @autoPackageIdentifiers;
+                    pop @packageIdentifiers;
+                    };
+
+                if (scalar @autoPackageIdentifiers)
+                    {  $topic->SetPackage( NaturalDocs::SymbolString->Join(@autoPackageIdentifiers) );  };
+                };
+
+            $topicIndex++;
+            $autoTopicIndex++;
+            }
+
+        # Extract topics in lists.
+        elsif ($topic->IsList())
+            {
+            if (!exists $topicsInLists{$topic->Type()})
+                {  $topicsInLists{$topic->Type()} = { };  };
+
+            my $body = $topic->Body();
+
+            while ($body =~ /<ds>([^<]+)<\/ds>/g)
+                {  $topicsInLists{$topic->Type()}->{NaturalDocs::NDMarkup->RestoreAmpChars($1)} = 1;  };
+
+            $topicIndex++;
+            }
+
+        # Otherwise there's no match.  Skip the topic.  The auto-topic will be added later.
+        else
+            {
+            $topicIndex++;
+            }
+        };
+
+    # Add any auto-topics remaining.
+    if (!NaturalDocs::Settings->DocumentedOnly())
+    	{
+	    while ($autoTopicIndex < scalar @$autoTopics)
+	        {
+	        my $autoTopic = $autoTopics->[$autoTopicIndex];
+
+	        if (exists $topicsInLists{$autoTopic->Type()} &&
+	            exists $topicsInLists{$autoTopic->Type()}->{$autoTopic->Title()})
+	            {
+	            # Remove it from the list so a second one with the same name will be added.
+	            delete $topicsInLists{$autoTopic->Type()}->{$autoTopic->Title()};
+	            }
+	        else
+	            {
+	            push(@parsedFile, $autoTopic);
+	            };
+
+	        $autoTopicIndex++;
+	        };
+        };
+   };
+
+
+#
+#   Function: RemoveRemainingHeaderlessTopics
+#
+#   After <MergeAutoTopics()> is done, this function removes any remaining headerless topics from the file.  If they don't merge
+#   into anything, they're not valid topics.
+#
+sub RemoveRemainingHeaderlessTopics
+    {
+    my ($self) = @_;
+
+    my $index = 0;
+    while ($index < scalar @parsedFile)
+        {
+        if ($parsedFile[$index]->Title())
+            {  $index++;  }
+        else
+            {  splice(@parsedFile, $index, 1);  };
+        };
+    };
+
+
+#
+#   Function: MakeAutoGroups
+#
+#   Creates group topics for files that do not have them.
+#
+sub MakeAutoGroups
+    {
+    my ($self) = @_;
+
+    # No groups only one topic.
+    if (scalar @parsedFile < 2)
+        {  return;  };
+
+    my $index = 0;
+    my $startStretch = 0;
+
+    # Skip the first entry if its the page title.
+    if (NaturalDocs::Topics->TypeInfo( $parsedFile[0]->Type() )->PageTitleIfFirst())
+        {
+        $index = 1;
+        $startStretch = 1;
+        };
+
+    # Make auto-groups for each stretch between scope-altering topics.
+    while ($index < scalar @parsedFile)
+        {
+        my $scope = NaturalDocs::Topics->TypeInfo($parsedFile[$index]->Type())->Scope();
+
+        if ($scope == ::SCOPE_START() || $scope == ::SCOPE_END())
+            {
+            if ($index > $startStretch)
+                {  $index += $self->MakeAutoGroupsFor($startStretch, $index);  };
+
+            $startStretch = $index + 1;
+            };
+
+        $index++;
+        };
+
+    if ($index > $startStretch)
+        {  $self->MakeAutoGroupsFor($startStretch, $index);  };
+    };
+
+
+#
+#   Function: MakeAutoGroupsFor
+#
+#   Creates group topics for sections of files that do not have them.  A support function for <MakeAutoGroups()>.
+#
+#   Parameters:
+#
+#       startIndex - The index to start at.
+#       endIndex - The index to end at.  Not inclusive.
+#
+#   Returns:
+#
+#       The number of group topics added.
+#
+sub MakeAutoGroupsFor #(startIndex, endIndex)
+    {
+    my ($self, $startIndex, $endIndex) = @_;
+
+    # No groups if any are defined already.
+    for (my $i = $startIndex; $i < $endIndex; $i++)
+        {
+        if ($parsedFile[$i]->Type() eq ::TOPIC_GROUP())
+            {  return 0;  };
+        };
+
+
+    use constant COUNT => 0;
+    use constant TYPE => 1;
+    use constant SECOND_TYPE => 2;
+    use constant SIZE => 3;
+
+    # This is an array of ( count, type, secondType ) triples.  Count and Type will always be filled in; count is the number of
+    # consecutive topics of type.  On the second pass, if small groups are combined secondType will be filled in.  There will not be
+    # more than two types per group.
+    my @groups;
+    my $groupIndex = 0;
+
+
+    # First pass: Determine all the groups.
+
+    my $i = $startIndex;
+    my $currentType;
+
+    while ($i < $endIndex)
+        {
+        if (!defined $currentType || ($parsedFile[$i]->Type() ne $currentType && $parsedFile[$i]->Type() ne ::TOPIC_GENERIC()) )
+            {
+            if (defined $currentType)
+                {  $groupIndex += SIZE;  };
+
+            $currentType = $parsedFile[$i]->Type();
+
+            $groups[$groupIndex + COUNT] = 1;
+            $groups[$groupIndex + TYPE] = $currentType;
+            }
+        else
+            {  $groups[$groupIndex + COUNT]++;  };
+
+        $i++;
+        };
+
+
+    # Second pass: Combine groups based on "noise".  Noise means types go from A to B to A at least once, and there are at least
+    # two groups in a row with three or less, and at least one of those groups is two or less.  So 3, 3, 3 doesn't count as noise, but
+    # 3, 2, 3 does.
+
+    $groupIndex = 0;
+
+    # While there are at least three groups left...
+    while ($groupIndex < scalar @groups - (2 * SIZE))
+        {
+        # If the group two places in front of this one has the same type...
+        if ($groups[$groupIndex + (2 * SIZE) + TYPE] eq $groups[$groupIndex + TYPE])
+            {
+            # It means we went from A to B to A, which partially qualifies as noise.
+
+            my $firstType = $groups[$groupIndex + TYPE];
+            my $secondType = $groups[$groupIndex + SIZE + TYPE];
+
+            if (NaturalDocs::Topics->TypeInfo($firstType)->CanGroupWith($secondType) ||
+                NaturalDocs::Topics->TypeInfo($secondType)->CanGroupWith($firstType))
+                {
+                my $hasNoise;
+
+                my $hasThrees;
+                my $hasTwosOrOnes;
+
+                my $endIndex = $groupIndex;
+
+                while ($endIndex < scalar @groups &&
+                         ($groups[$endIndex + TYPE] eq $firstType || $groups[$endIndex + TYPE] eq $secondType))
+                    {
+                    if ($groups[$endIndex + COUNT] > 3)
+                        {
+                        # They must be consecutive to count.
+                        $hasThrees = 0;
+                        $hasTwosOrOnes = 0;
+                        }
+                    elsif ($groups[$endIndex + COUNT] == 3)
+                        {
+                        $hasThrees = 1;
+
+                        if ($hasTwosOrOnes)
+                            {  $hasNoise = 1;  };
+                        }
+                    else # < 3
+                        {
+                        if ($hasThrees || $hasTwosOrOnes)
+                            {  $hasNoise = 1;  };
+
+                        $hasTwosOrOnes = 1;
+                        };
+
+                    $endIndex += SIZE;
+                    };
+
+                if (!$hasNoise)
+                    {
+                    $groupIndex = $endIndex - SIZE;
+                    }
+                else # hasNoise
+                    {
+                    $groups[$groupIndex + SECOND_TYPE] = $secondType;
+
+                    for (my $noiseIndex = $groupIndex + SIZE; $noiseIndex < $endIndex; $noiseIndex += SIZE)
+                        {
+                        $groups[$groupIndex + COUNT] += $groups[$noiseIndex + COUNT];
+                        };
+
+                    splice(@groups, $groupIndex + SIZE, $endIndex - $groupIndex - SIZE);
+
+                    $groupIndex += SIZE;
+                    };
+                }
+
+            else # They can't group together
+                {
+                $groupIndex += SIZE;
+                };
+            }
+
+        else
+            {  $groupIndex += SIZE;  };
+        };
+
+
+    # Finally, create group topics for the parsed file.
+
+    $groupIndex = 0;
+    $i = $startIndex;
+
+    while ($groupIndex < scalar @groups)
+        {
+        if ($groups[$groupIndex + TYPE] ne ::TOPIC_GENERIC())
+            {
+            my $topic = $parsedFile[$i];
+            my $title = NaturalDocs::Topics->NameOfType($groups[$groupIndex + TYPE], 1);
+
+            if (defined $groups[$groupIndex + SECOND_TYPE])
+                {  $title .= ' and ' . NaturalDocs::Topics->NameOfType($groups[$groupIndex + SECOND_TYPE], 1);  };
+
+            splice(@parsedFile, $i, 0, NaturalDocs::Parser::ParsedTopic->New(::TOPIC_GROUP(),
+                                                                                                            $title,
+                                                                                                            $topic->Package(), $topic->Using(),
+                                                                                                            undef, undef, undef,
+                                                                                                            $topic->LineNumber()) );
+            $i++;
+            };
+
+        $i += $groups[$groupIndex + COUNT];
+        $groupIndex += SIZE;
+        };
+
+    return (scalar @groups / SIZE);
+    };
+
+
+#
+#   Function: AddToClassHierarchy
+#
+#   Adds any class topics to the class hierarchy, since they may not have been called with <OnClass()> if they didn't match up to
+#   an auto-topic.
+#
+sub AddToClassHierarchy
+    {
+    my ($self) = @_;
+
+    foreach my $topic (@parsedFile)
+        {
+        if ($topic->Type() && NaturalDocs::Topics->TypeInfo( $topic->Type() )->ClassHierarchy())
+            {
+            if ($topic->IsList())
+                {
+                my $body = $topic->Body();
+
+                while ($body =~ /<ds>([^<]+)<\/ds>/g)
+                    {
+                    $self->OnClass( NaturalDocs::SymbolString->FromText( NaturalDocs::NDMarkup->RestoreAmpChars($1) ) );
+                    };
+                }
+            else
+                {
+                $self->OnClass($topic->Package());
+                };
+            };
+        };
+    };
+
+
+#
+#   Function: AddPackageDelineators
+#
+#   Adds section and class topics to make sure the package is correctly represented in the documentation.  Should be called last in
+#   this process.
+#
+sub AddPackageDelineators
+    {
+    my ($self) = @_;
+
+    my $index = 0;
+    my $currentPackage;
+
+    # Values are the arrayref [ title, type ];
+    my %usedPackages;
+
+    while ($index < scalar @parsedFile)
+        {
+        my $topic = $parsedFile[$index];
+
+        if ($topic->Package() ne $currentPackage)
+            {
+            $currentPackage = $topic->Package();
+            my $scopeType = NaturalDocs::Topics->TypeInfo($topic->Type())->Scope();
+
+            if ($scopeType == ::SCOPE_START())
+                {
+                $usedPackages{$currentPackage} = [ $topic->Title(), $topic->Type() ];
+                }
+            elsif ($scopeType == ::SCOPE_END())
+                {
+                my $newTopic;
+
+                if (!defined $currentPackage)
+                    {
+                    $newTopic = NaturalDocs::Parser::ParsedTopic->New(::TOPIC_SECTION(), 'Global',
+                                                                                                   undef, undef,
+                                                                                                   undef, undef, undef,
+                                                                                                   $topic->LineNumber(), undef);
+                    }
+                else
+                    {
+                    my ($title, $body, $summary, $type);
+                    my @packageIdentifiers = NaturalDocs::SymbolString->IdentifiersOf($currentPackage);
+
+                    if (exists $usedPackages{$currentPackage})
+                        {
+                        $title = $usedPackages{$currentPackage}->[0];
+                        $type = $usedPackages{$currentPackage}->[1];
+                        $body = '<p>(continued)</p>';
+                        $summary = '(continued)';
+                        }
+                    else
+                        {
+                        $title = join($language->PackageSeparator(), @packageIdentifiers);
+                        $type = ::TOPIC_CLASS();
+
+                        # Body and summary stay undef.
+
+                        $usedPackages{$currentPackage} = $title;
+                        };
+
+                    my @titleIdentifiers = NaturalDocs::SymbolString->IdentifiersOf( NaturalDocs::SymbolString->FromText($title) );
+                    for (my $i = 0; $i < scalar @titleIdentifiers; $i++)
+                        {  pop @packageIdentifiers;  };
+
+                    $newTopic = NaturalDocs::Parser::ParsedTopic->New($type, $title,
+                                                                                                   NaturalDocs::SymbolString->Join(@packageIdentifiers), undef,
+                                                                                                   undef, $summary, $body,
+                                                                                                   $topic->LineNumber(), undef);
+                    }
+
+                splice(@parsedFile, $index, 0, $newTopic);
+                $index++;
+                }
+            };
+
+        $index++;
+        };
+    };
+
+
+#
+#   Function: BreakLists
+#
+#   Breaks list topics into individual topics.
+#
+sub BreakLists
+    {
+    my $self = shift;
+
+    my $index = 0;
+
+    while ($index < scalar @parsedFile)
+        {
+        my $topic = $parsedFile[$index];
+
+        if ($topic->IsList() && NaturalDocs::Topics->TypeInfo( $topic->Type() )->BreakLists())
+            {
+            my $body = $topic->Body();
+
+            my @newTopics;
+            my $newBody;
+
+            my $bodyIndex = 0;
+
+            for (;;)
+                {
+                my $startList = index($body, '<dl>', $bodyIndex);
+
+                if ($startList == -1)
+                    {  last;  };
+
+                $newBody .= substr($body, $bodyIndex, $startList - $bodyIndex);
+
+                my $endList = index($body, '</dl>', $startList);
+                my $listBody = substr($body, $startList, $endList - $startList);
+
+                while ($listBody =~ /<ds>([^<]+)<\/ds><dd>(.*?)<\/dd>/g)
+                    {
+                    my ($symbol, $description) = ($1, $2);
+
+                    push @newTopics, NaturalDocs::Parser::ParsedTopic->New( $topic->Type(), $symbol, $topic->Package(),
+                                                                                                            $topic->Using(), undef,
+                                                                                                            $self->GetSummaryFromDescriptionList($description),
+                                                                                                            '<p>' . $description .  '</p>', $topic->LineNumber(),
+                                                                                                            undef );
+                    };
+
+                $bodyIndex = $endList + 5;
+                };
+
+            $newBody .= substr($body, $bodyIndex);
+
+            # Remove trailing headings.
+            $newBody =~ s/(?:<h>[^<]+<\/h>)+$//;
+
+            # Remove empty headings.
+            $newBody =~ s/(?:<h>[^<]+<\/h>)+(<h>[^<]+<\/h>)/$1/g;
+
+            if ($newBody)
+                {
+                unshift @newTopics, NaturalDocs::Parser::ParsedTopic->New( ::TOPIC_GROUP(), $topic->Title(), $topic->Package(),
+                                                                                                          $topic->Using(), undef,
+                                                                                                          $self->GetSummaryFromBody($newBody), $newBody,
+                                                                                                          $topic->LineNumber(), undef );
+                };
+
+            splice(@parsedFile, $index, 1, @newTopics);
+
+            $index += scalar @newTopics;
+            }
+
+        else # not a list
+            {  $index++;  };
+        };
+    };
+
+
+#
+#   Function: GetSummaryFromBody
+#
+#   Returns the summary text from the topic body.
+#
+#   Parameters:
+#
+#       body - The complete topic body, in <NDMarkup>.
+#
+#   Returns:
+#
+#       The topic summary, or undef if none.
+#
+sub GetSummaryFromBody #(body)
+    {
+    my ($self, $body) = @_;
+
+    my $summary;
+
+    # Extract the first sentence from the leading paragraph, if any.  We'll tolerate a single header beforehand, but nothing else.
+
+    if ($body =~ /^(?:<h>[^<]*<\/h>)?<p>(.*?)(<\/p>|[\.\!\?](?:[\)\}\'\ ]|&quot;|&gt;))/x)
+        {
+        $summary = $1;
+
+        if ($2 ne '</p>')
+            {  $summary .= $2;  };
+        };
+
+    return $summary;
+    };
+
+
+#
+#   Function: GetSummaryFromDescriptionList
+#
+#   Returns the summary text from a description list entry.
+#
+#   Parameters:
+#
+#       description - The description in <NDMarkup>.  Should be the content between the <dd></dd> tags only.
+#
+#   Returns:
+#
+#       The description summary, or undef if none.
+#
+sub GetSummaryFromDescriptionList #(description)
+    {
+    my ($self, $description) = @_;
+
+    my $summary;
+
+    if ($description =~ /^(.*?)($|[\.\!\?](?:[\)\}\'\ ]|&quot;|&gt;))/)
+        {  $summary = $1 . $2;  };
+
+    return $summary;
+    };
+
+
+1;

Added: trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Project/ImageFile.pm
===================================================================
--- trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Project/ImageFile.pm	                        (rev 0)
+++ trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Project/ImageFile.pm	2009-07-01 15:30:00 UTC (rev 4275)
@@ -0,0 +1,160 @@
+###############################################################################
+#
+#   Class: NaturalDocs::Project::ImageFile
+#
+###############################################################################
+#
+#   A simple information class about project image files.
+#
+###############################################################################
+
+# 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::Project::ImageFile;
+
+
+
+###############################################################################
+# Group: Implementation
+
+#
+#   Constants: Members
+#
+#   The class is implemented as a blessed arrayref.  The following constants are used as indexes.
+#
+#       LAST_MODIFIED - The integer timestamp of when the file was last modified.
+#       STATUS - <FileStatus> since the last build.
+#       REFERENCE_COUNT - The number of references to the image from the source files.
+#       WAS_USED - Whether the image was used the last time Natural Docs was run.
+#       WIDTH - The image width.  Undef if can't be determined, -1 if haven't attempted to determine yet.
+#       HEIGHT - The image height.  Undef if can't be determined, -1 if haven't attempted to determine yet.
+#
+
+use NaturalDocs::DefineMembers 'LAST_MODIFIED', 'LastModified()', 'SetLastModified()',
+                                                 'STATUS', 'Status()', 'SetStatus()',
+                                                 'REFERENCE_COUNT', 'ReferenceCount()',
+                                                 'WAS_USED', 'WasUsed()', 'SetWasUsed()',
+                                                 'WIDTH', 'Width()',
+                                                 'HEIGHT', 'Height()';
+
+
+#
+#   Topic: WasUsed versus References
+#
+#   <WasUsed()> is a simple true/false that notes whether this image file was used the last time Natural Docs was run.
+#   <ReferenceCount()> is a counter for the number of times it's used *this* run.  As such, it starts at zero regardless of whether
+#   <WasUsed()> is set or not.
+#
+
+
+###############################################################################
+# Group: Functions
+
+#
+#   Function: New
+#
+#   Creates and returns a new file object.
+#
+#   Parameters:
+#
+#       lastModified - The image file's last modification timestamp
+#       status - The <FileStatus>.
+#       wasUsed - Whether this image file was used the *last* time Natural Docs was run.
+#
+sub New #(timestamp lastModified, FileStatus status, bool wasUsed)
+    {
+    my ($package, $lastModified, $status, $width, $height, $wasUsed) = @_;
+
+    my $object = [ ];
+    $object->[LAST_MODIFIED] = $lastModified;
+    $object->[STATUS] = $status;
+    $object->[REFERENCE_COUNT] = 0;
+    $object->[WAS_USED] = $wasUsed;
+    $object->[WIDTH] = -1;
+    $object->[HEIGHT] = -1;
+
+    bless $object, $package;
+
+    return $object;
+    };
+
+
+#
+#   Functions: Member Functions
+#
+#   LastModified - Returns the integer timestamp of when the file was last modified.
+#   SetLastModified - Sets the file's last modification timestamp.
+#   Status - Returns the <FileStatus> since the last build.
+#   SetStatus - Sets the <FileStatus> since the last build.
+#
+
+#
+#   Function: ReferenceCount
+#   Returns the current number of references to this image file during *this* Natural Docs execution.
+#
+
+#
+#   Function: AddReference
+#   Increases the number of references to this image file by one.  Returns the new reference count.
+#
+sub AddReference
+    {
+    my $self = shift;
+
+    $self->[REFERENCE_COUNT]++;
+    return $self->[REFERENCE_COUNT];
+    };
+
+#
+#   Function: DeleteReference
+#   Decreases the number of references to this image file by one.  Returns the new reference count.
+#
+sub DeleteReference
+    {
+    my $self = shift;
+    $self->[REFERENCE_COUNT]--;
+
+    if ($self->[REFERENCE_COUNT] < 0)
+        {  die "Deleted more references to an image file than existed.";  };
+
+    return $self->[REFERENCE_COUNT];
+    };
+
+
+#
+#   Functions: Member Functions
+#
+#   WasUsed - Returns whether this image file was used during the *last* Natural Docs execution.
+#   SetWasUsed - Sets whether this image file was used during the *last* Natural Docs execution.
+#   Width - Returns the width in pixels, undef if it can't be determined, and -1 if determination hasn't been attempted yet.
+#   Height - Returns the width in pixels, undef if it can't be determined, and -1 if determination hasn't been attempted yet.
+#
+
+
+#
+#   Function: SetDimensions
+#   Sets the width and height of the image.  Set to undef if they can't be determined.
+#
+sub SetDimensions #(int width, int height)
+    {
+    my ($self, $width, $height) = @_;
+
+    # If either are undef, both should be undef.  This will also convert zeroes to undef.
+    if (!$width || !$height)
+        {
+        $self->[WIDTH] = undef;
+        $self->[HEIGHT] = undef;
+        }
+    else
+        {
+        $self->[WIDTH] = $width;
+        $self->[HEIGHT] = $height;
+        };
+    };
+
+
+1;

Added: trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Project/SourceFile.pm
===================================================================
--- trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Project/SourceFile.pm	                        (rev 0)
+++ trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Project/SourceFile.pm	2009-07-01 15:30:00 UTC (rev 4275)
@@ -0,0 +1,113 @@
+###############################################################################
+#
+#   Class: NaturalDocs::Project::SourceFile
+#
+###############################################################################
+#
+#   A simple information class about project files.
+#
+###############################################################################
+
+# 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::Project::SourceFile;
+
+
+
+###############################################################################
+# Group: Implementation
+
+#
+#   Constants: Members
+#
+#   The class is implemented as a blessed arrayref.  The following constants are used as indexes.
+#
+#       HAS_CONTENT             - Whether the file contains Natural Docs content or not.
+#       LAST_MODIFIED           - The integer timestamp of when the file was last modified.
+#       STATUS                       - <FileStatus> since the last build.
+#       DEFAULT_MENU_TITLE  - The file's default title in the menu.
+#
+
+# DEPENDENCY: New() depends on its parameter list being in the same order as these constants.  If the order changes, New()
+# needs to be changed.
+use NaturalDocs::DefineMembers 'HAS_CONTENT', 'LAST_MODIFIED', 'STATUS', 'DEFAULT_MENU_TITLE';
+
+
+###############################################################################
+# Group: Functions
+
+#
+#   Function: New
+#
+#   Creates and returns a new file object.
+#
+#   Parameters:
+#
+#       hasContent         - Whether the file contains Natural Docs content or not.
+#       lastModified         - The integer timestamp of when the file was last modified.
+#       status                 - The <FileStatus> since the last build.
+#       defaultMenuTitle  - The file's title in the menu.
+#
+#   Returns:
+#
+#       A reference to the new object.
+#
+sub New #(hasContent, lastModified, status, defaultMenuTitle)
+    {
+    # DEPENDENCY: This function depends on its parameter list being in the same order as the member constants.  If either order
+    # changes, this function needs to be changed.
+
+    my $package = shift;
+
+    my $object = [ @_ ];
+    bless $object, $package;
+
+    return $object;
+    };
+
+# Function: HasContent
+# Returns whether the file contains Natural Docs content or not.
+sub HasContent
+    {  return $_[0]->[HAS_CONTENT];  };
+
+# Function: SetHasContent
+# Sets whether the file contains Natural Docs content or not.
+sub SetHasContent #(hasContent)
+    {  $_[0]->[HAS_CONTENT] = $_[1];  };
+
+# Function: LastModified
+# Returns the integer timestamp of when the file was last modified.
+sub LastModified
+    {  return $_[0]->[LAST_MODIFIED];  };
+
+# Function: SetLastModified
+# Sets the file's last modification timestamp.
+sub SetLastModified #(lastModified)
+    {  $_[0]->[LAST_MODIFIED] = $_[1];  };
+
+# Function: Status
+# Returns the <FileStatus> since the last build.
+sub Status
+    {  return $_[0]->[STATUS];  };
+
+# Function: SetStatus
+# Sets the <FileStatus> since the last build.
+sub SetStatus #(status)
+    {  $_[0]->[STATUS] = $_[1];  };
+
+# Function: DefaultMenuTitle
+# Returns the file's default title on the menu.
+sub DefaultMenuTitle
+    {  return $_[0]->[DEFAULT_MENU_TITLE];  };
+
+# Function: SetDefaultMenuTitle
+# Sets the file's default title on the menu.
+sub SetDefaultMenuTitle #(menuTitle)
+    {  $_[0]->[DEFAULT_MENU_TITLE] = $_[1];  };
+
+
+1;

Added: trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Project.pm
===================================================================
--- trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Project.pm	                        (rev 0)
+++ trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Project.pm	2009-07-01 15:30:00 UTC (rev 4275)
@@ -0,0 +1,1402 @@
+###############################################################################
+#
+#   Package: NaturalDocs::Project
+#
+###############################################################################
+#
+#   A package that manages information about the files in the source tree, as well as the list of files that have to be parsed
+#   and built.
+#
+#   Usage and Dependencies:
+#
+#       - All the <Config and Data File Functions> are available immediately, except for the status functions.
+#
+#       - <ReparseEverything()> and <RebuildEverything()> are available immediately, because they may need to be called
+#         after <LoadConfigFileInfo()> but before <LoadSourceFileInfo()>.
+#
+#       - Prior to <LoadConfigFileInfo()>, <NaturalDocs::Settings> must be initialized.
+#
+#       - After <LoadConfigFileInfo()>, the status <Config and Data File Functions> are available as well.
+#
+#       - Prior to <LoadSourceFileInfo()>, <NaturalDocs::Settings> and <NaturalDocs::Languages> must be initialized.
+#
+#       - After <LoadSourceFileInfo()>, the rest of the <Source File Functions> are available.
+#
+###############################################################################
+
+# This file is part of Natural Docs, which is Copyright (C) 2003-2008 Greg Valure
+# Natural Docs is licensed under the GPL
+
+use NaturalDocs::Project::SourceFile;
+use NaturalDocs::Project::ImageFile;
+
+use strict;
+use integer;
+
+package NaturalDocs::Project;
+
+
+###############################################################################
+# Group: File Handles
+
+#
+#   handle: FH_FILEINFO
+#
+#   The file handle for the file information file, <FileInfo.nd>.
+#
+
+#
+#   handle: FH_CONFIGFILEINFO
+#
+#   The file handle for the config file information file, <ConfigFileInfo.nd>.
+#
+
+#
+#   handle: FH_IMAGEFILE
+#
+#   The file handle for determining the dimensions of image files.
+#
+
+
+
+###############################################################################
+# Group: Source File Variables
+
+
+#
+#   hash: supportedFiles
+#
+#   A hash of all the supported files in the input directory.  The keys are the <FileNames>, and the values are
+#   <NaturalDocs::Project::SourceFile> objects.
+#
+my %supportedFiles;
+
+#
+#   hash: filesToParse
+#
+#   An existence hash of all the <FileNames> that need to be parsed.
+#
+my %filesToParse;
+
+#
+#   hash: filesToBuild
+#
+#   An existence hash of all the <FileNames> that need to be built.
+#
+my %filesToBuild;
+
+#
+#   hash: filesToPurge
+#
+#   An existence hash of the <FileNames> that had Natural Docs content last time, but now either don't exist or no longer have
+#   content.
+#
+my %filesToPurge;
+
+#
+#   hash: unbuiltFilesWithContent
+#
+#   An existence hash of all the <FileNames> that have Natural Docs content but are not part of <filesToBuild>.
+#
+my %unbuiltFilesWithContent;
+
+
+# bool: reparseEverything
+# Whether all the source files need to be reparsed.
+my $reparseEverything;
+
+# bool: rebuildEverything
+# Whether all the source files need to be rebuilt.
+my $rebuildEverything;
+
+# hash: mostUsedLanguage
+# The name of the most used language.  Doesn't include text files.
+my $mostUsedLanguage;
+
+
+
+###############################################################################
+# Group: Configuration File Variables
+
+
+#
+#   hash: mainConfigFile
+#
+#   A hash mapping all the main configuration file names without paths to their <FileStatus>.  Prior to <LoadConfigFileInfo()>,
+#   it serves as an existence hashref of the file names.
+#
+my %mainConfigFiles = ( 'Topics.txt' => 1, 'Languages.txt' => 1 );
+
+#
+#   hash: userConfigFiles
+#
+#   A hash mapping all the user configuration file names without paths to their <FileStatus>.  Prior to <LoadConfigFileInfo()>,
+#   it serves as an existence hashref of the file names.
+#
+my %userConfigFiles = ( 'Topics.txt' => 1, 'Languages.txt' => 1, 'Menu.txt' => 1 );
+
+
+
+
+###############################################################################
+# Group: Image File Variables
+
+
+#
+#   hash: imageFileExtensions
+#
+#   An existence hash of all the file extensions for images.  Extensions are in all lowercase.
+#
+my %imageFileExtensions = ( 'jpg' => 1, 'jpeg' => 1, 'gif' => 1, 'png' => 1, 'bmp' => 1 );
+
+
+#
+#   hash: imageFiles
+#
+#   A hash of all the image files in the project.  The keys are the <FileNames> and the values are
+#   <NaturalDocs::Project::ImageFiles>.
+#
+my %imageFiles;
+
+
+#
+#   hash: imageFilesToUpdate
+#
+#   An existence hash of all the image <FileNames> that need to be updated, either because they changed or they're new to the
+#   project.
+#
+my %imageFilesToUpdate;
+
+
+#
+#   hash: imageFilesToPurge
+#
+#   An existence hash of all the image <FileNames> that need to be purged, either because the files no longer exist or because
+#   they are no longer used.
+#
+my %imageFilesToPurge;
+
+
+#
+#   hash: insensitiveImageFiles
+#
+#   A hash that maps all lowercase image <FileNames> to their proper case as it would appear in <imageFiles>.  Used for
+#   case insensitivity, obviously.
+#
+#   You can't just use all lowercase in <imageFiles> because both Linux and HTTP are case sensitive, so the original case must
+#   be preserved.  We also want to allow separate entries for files that differ based only on case, so it goes to <imageFiles> first
+#   where they can be distinguished and here only if there's no match.  Ties are broken by whichever is lower with cmp, because
+#   it has to resolve consistently on all runs of the program.
+#
+my %insensitiveImageFiles;
+
+
+
+###############################################################################
+# Group: Files
+
+
+#
+#   File: FileInfo.nd
+#
+#   An index of the state of the files as of the last parse.  Used to determine if files were added, deleted, or changed.
+#
+#   Format:
+#
+#       The format is a text file.
+#
+#       > [VersionInt: app version]
+#
+#       The beginning of the file is the <VersionInt> it was generated with.
+#
+#       > [most used language name]
+#
+#       Next is the name of the most used language in the source tree.  Does not include text files.
+#
+#       Each following line is
+#
+#       > [file name] tab [last modification time] tab [has ND content (0 or 1)] tab [default menu title] \n
+#
+#   Revisions:
+#
+#       1.3:
+#
+#           - The line following the <VersionInt>, which was previously the last modification time of <Menu.txt>, was changed to
+#             the name of the most used language.
+#
+#       1.16:
+#
+#           - File names are now absolute.  Prior to 1.16, they were relative to the input directory since only one was allowed.
+#
+#       1.14:
+#
+#           - The file was renamed from NaturalDocs.files to FileInfo.nd and moved into the Data subdirectory.
+#
+#       0.95:
+#
+#           - The file version was changed to match the program version.  Prior to 0.95, the version line was 1.  Test for "1" instead
+#             of "1.0" to distinguish.
+#
+
+
+#
+#   File: ConfigFileInfo.nd
+#
+#   An index of the state of the config files as of the last parse.
+#
+#   Format:
+#
+#       > [BINARY_FORMAT]
+#       > [VersionInt: app version]
+#
+#       First is the standard <BINARY_FORMAT> <VersionInt> header.
+#
+#       > [UInt32: last modification time of menu]
+#       > [UInt32: last modification of main topics file]
+#       > [UInt32: last modification of user topics file]
+#       > [UInt32: last modification of main languages file]
+#       > [UInt32: last modification of user languages file]
+#
+#       Next are the last modification times of various configuration files as UInt32s in the standard Unix format.
+#
+#
+#   Revisions:
+#
+#       1.3:
+#
+#           - The file was added to Natural Docs.  Previously the last modification of <Menu.txt> was stored in <FileInfo.nd>, and
+#             <Topics.txt> and <Languages.txt> didn't exist.
+#
+
+
+#
+#   File: ImageFileInfo.nd
+#
+#   An index of the state of the image files as of the last parse.
+#
+#   Format:
+#
+#       > [Standard Binary Header]
+#
+#       First is the standard binary file header as defined by <NaturalDocs::BinaryFile>.
+#
+#       > [AString16: file name or undef]
+#       > [UInt32: last modification time]
+#       > [UInt8: was used]
+#
+#       This section is repeated until the file name is null.  The last modification times are UInt32s in the standard Unix format.
+#
+#
+#   Revisions:
+#
+#       1.4:
+#
+#           - The file was added to Natural Docs.
+#
+
+
+
+###############################################################################
+# Group: File Functions
+
+#
+#   Function: LoadSourceFileInfo
+#
+#   Loads the project file from disk and compares it against the files in the input directory.  Project is loaded from
+#   <FileInfo.nd>.  New and changed files will be added to <FilesToParse()>, and if they have content,
+#   <FilesToBuild()>.
+#
+#   Will call <NaturalDocs::Languages->OnMostUsedLanguageKnown()> if <MostUsedLanguage()> changes.
+#
+#   Returns:
+#
+#       Returns whether the project was changed in any way.
+#
+sub LoadSourceFileInfo
+    {
+    my ($self) = @_;
+
+    $self->GetAllSupportedFiles();
+    NaturalDocs::Languages->OnMostUsedLanguageKnown();
+
+    my $fileIsOkay;
+    my $version;
+    my $hasChanged;
+
+    if (open(FH_FILEINFO, '<' . $self->DataFile('FileInfo.nd')))
+        {
+        # Check if the file is in the right format.
+        $version = NaturalDocs::Version->FromTextFile(\*FH_FILEINFO);
+
+        # The project file need to be rebuilt for 1.16.  The source files need to be reparsed and the output files rebuilt for 1.4.
+        # We'll tolerate the difference between 1.16 and 1.3 in the loader.
+
+        if (NaturalDocs::Version->CheckFileFormat( $version, NaturalDocs::Version->FromString('1.16') ))
+            {
+            $fileIsOkay = 1;
+
+            if (!NaturalDocs::Version->CheckFileFormat( $version, NaturalDocs::Version->FromString('1.4') ))
+                {
+                $reparseEverything = 1;
+                $rebuildEverything = 1;
+                $hasChanged = 1;
+                };
+            }
+        else
+            {
+            close(FH_FILEINFO);
+            $hasChanged = 1;
+            };
+        };
+
+
+    if ($fileIsOkay)
+        {
+        my %indexedFiles;
+
+
+        my $line = <FH_FILEINFO>;
+        ::XChomp(\$line);
+
+        # Prior to 1.3 it was the last modification time of Menu.txt, which we ignore and treat as though the most used language
+        # changed.  Prior to 1.32 the settings didn't transfer over correctly to Menu.txt so we need to behave that way again.
+        if ($version < NaturalDocs::Version->FromString('1.32') || lc($mostUsedLanguage) ne lc($line))
+            {
+            $reparseEverything = 1;
+            NaturalDocs::SymbolTable->RebuildAllIndexes();
+            };
+
+
+        # Parse the rest of the file.
+
+        while ($line = <FH_FILEINFO>)
+            {
+            ::XChomp(\$line);
+            my ($file, $modification, $hasContent, $menuTitle) = split(/\t/, $line, 4);
+
+            # If the file no longer exists...
+            if (!exists $supportedFiles{$file})
+                {
+                if ($hasContent)
+                    {  $filesToPurge{$file} = 1;  };
+
+                $hasChanged = 1;
+                }
+
+            # If the file still exists...
+            else
+                {
+                $indexedFiles{$file} = 1;
+
+                # If the file changed...
+                if ($supportedFiles{$file}->LastModified() != $modification)
+                    {
+                    $supportedFiles{$file}->SetStatus(::FILE_CHANGED());
+                    $filesToParse{$file} = 1;
+
+                    # If the file loses its content, this will be removed by SetHasContent().
+                    if ($hasContent)
+                        {  $filesToBuild{$file} = 1;  };
+
+                    $hasChanged = 1;
+                    }
+
+                # If the file has not changed...
+                else
+                    {
+                    my $status;
+
+                    if ($rebuildEverything && $hasContent)
+                        {
+                        $status = ::FILE_CHANGED();
+
+                        # If the file loses its content, this will be removed by SetHasContent().
+                        $filesToBuild{$file} = 1;
+                        $hasChanged = 1;
+                        }
+                    else
+                        {
+                        $status = ::FILE_SAME();
+
+                        if ($hasContent)
+                            {  $unbuiltFilesWithContent{$file} = 1;  };
+                        };
+
+                    if ($reparseEverything)
+                        {
+                        $status = ::FILE_CHANGED();
+
+                        $filesToParse{$file} = 1;
+                        $hasChanged = 1;
+                        };
+
+                    $supportedFiles{$file}->SetStatus($status);
+                    };
+
+                $supportedFiles{$file}->SetHasContent($hasContent);
+                $supportedFiles{$file}->SetDefaultMenuTitle($menuTitle);
+                };
+            };
+
+        close(FH_FILEINFO);
+
+
+        # Check for added files.
+
+        if (scalar keys %supportedFiles > scalar keys %indexedFiles)
+            {
+            foreach my $file (keys %supportedFiles)
+                {
+                if (!exists $indexedFiles{$file})
+                    {
+                    $supportedFiles{$file}->SetStatus(::FILE_NEW());
+                    $supportedFiles{$file}->SetDefaultMenuTitle($file);
+                    $supportedFiles{$file}->SetHasContent(undef);
+                    $filesToParse{$file} = 1;
+                    # It will be added to filesToBuild if HasContent gets set to true when it's parsed.
+                    $hasChanged = 1;
+                    };
+                };
+            };
+        }
+
+    # If something's wrong with FileInfo.nd, everything is new.
+    else
+        {
+        foreach my $file (keys %supportedFiles)
+            {
+            $supportedFiles{$file}->SetStatus(::FILE_NEW());
+            $supportedFiles{$file}->SetDefaultMenuTitle($file);
+            $supportedFiles{$file}->SetHasContent(undef);
+            $filesToParse{$file} = 1;
+            # It will be added to filesToBuild if HasContent gets set to true when it's parsed.
+            };
+
+        $hasChanged = 1;
+        };
+
+
+    # There are other side effects, so we need to call this.
+    if ($rebuildEverything)
+        {  $self->RebuildEverything();  };
+
+
+    return $hasChanged;
+    };
+
+
+#
+#   Function: SaveSourceFileInfo
+#
+#   Saves the source file info to disk.  Everything is saved in <FileInfo.nd>.
+#
+sub SaveSourceFileInfo
+    {
+    my ($self) = @_;
+
+    open(FH_FILEINFO, '>' . $self->DataFile('FileInfo.nd'))
+        or die "Couldn't save project file " . $self->DataFile('FileInfo.nd') . "\n";
+
+    NaturalDocs::Version->ToTextFile(\*FH_FILEINFO, NaturalDocs::Settings->AppVersion());
+
+    print FH_FILEINFO $mostUsedLanguage . "\n";
+
+    while (my ($fileName, $file) = each %supportedFiles)
+        {
+        print FH_FILEINFO $fileName . "\t"
+                              . $file->LastModified() . "\t"
+                              . ($file->HasContent() || '0') . "\t"
+                              . $file->DefaultMenuTitle() . "\n";
+        };
+
+    close(FH_FILEINFO);
+    };
+
+
+#
+#   Function: LoadConfigFileInfo
+#
+#   Loads the config file info from disk.
+#
+sub LoadConfigFileInfo
+    {
+    my ($self) = @_;
+
+    my $fileIsOkay;
+    my $version;
+    my $fileName = NaturalDocs::Project->DataFile('ConfigFileInfo.nd');
+
+    if (open(FH_CONFIGFILEINFO, '<' . $fileName))
+        {
+        # See if it's binary.
+        binmode(FH_CONFIGFILEINFO);
+
+        my $firstChar;
+        read(FH_CONFIGFILEINFO, $firstChar, 1);
+
+        if ($firstChar == ::BINARY_FORMAT())
+            {
+            $version = NaturalDocs::Version->FromBinaryFile(\*FH_CONFIGFILEINFO);
+
+            # It hasn't changed since being introduced.
+
+            if (NaturalDocs::Version->CheckFileFormat($version))
+                {  $fileIsOkay = 1;  }
+            else
+                {  close(FH_CONFIGFILEINFO);  };
+            }
+
+        else # it's not in binary
+            {  close(FH_CONFIGFILEINFO);  };
+        };
+
+    my @configFiles = ( $self->UserConfigFile('Menu.txt'), \$userConfigFiles{'Menu.txt'},
+                                 $self->MainConfigFile('Topics.txt'), \$mainConfigFiles{'Topics.txt'},
+                                 $self->UserConfigFile('Topics.txt'), \$userConfigFiles{'Topics.txt'},
+                                 $self->MainConfigFile('Languages.txt'), \$mainConfigFiles{'Languages.txt'},
+                                 $self->UserConfigFile('Languages.txt'), \$userConfigFiles{'Languages.txt'} );
+
+    if ($fileIsOkay)
+        {
+        my $raw;
+
+        read(FH_CONFIGFILEINFO, $raw, 20);
+        my @configFileDates = unpack('NNNNN', $raw);
+
+        while (scalar @configFiles)
+            {
+            my $file = shift @configFiles;
+            my $fileStatus = shift @configFiles;
+            my $fileDate = shift @configFileDates;
+
+            if (-e $file)
+                {
+                if ($fileDate == (stat($file))[9])
+                    {  $$fileStatus = ::FILE_SAME();  }
+                else
+                    {  $$fileStatus = ::FILE_CHANGED();  };
+                }
+            else
+                {  $$fileStatus = ::FILE_DOESNTEXIST();  };
+            };
+
+        close(FH_CONFIGFILEINFO);
+        }
+    else # !$fileIsOkay
+        {
+        while (scalar @configFiles)
+            {
+            my $file = shift @configFiles;
+            my $fileStatus = shift @configFiles;
+
+            if (-e $file)
+                {  $$fileStatus = ::FILE_CHANGED();  }
+            else
+                {  $$fileStatus = ::FILE_DOESNTEXIST();  };
+            };
+        };
+
+    if ($userConfigFiles{'Menu.txt'} == ::FILE_SAME() && $rebuildEverything)
+        {  $userConfigFiles{'Menu.txt'} = ::FILE_CHANGED();  };
+    };
+
+
+#
+#   Function: SaveConfigFileInfo
+#
+#   Saves the config file info to disk.  You *must* save all other config files first, such as <Menu.txt> and <Topics.txt>.
+#
+sub SaveConfigFileInfo
+    {
+    my ($self) = @_;
+
+    open (FH_CONFIGFILEINFO, '>' . NaturalDocs::Project->DataFile('ConfigFileInfo.nd'))
+        or die "Couldn't save " . NaturalDocs::Project->DataFile('ConfigFileInfo.nd') . ".\n";
+
+    binmode(FH_CONFIGFILEINFO);
+
+    print FH_CONFIGFILEINFO '' . ::BINARY_FORMAT();
+
+    NaturalDocs::Version->ToBinaryFile(\*FH_CONFIGFILEINFO, NaturalDocs::Settings->AppVersion());
+
+    print FH_CONFIGFILEINFO pack('NNNNN', (stat($self->UserConfigFile('Menu.txt')))[9],
+                                                                (stat($self->MainConfigFile('Topics.txt')))[9],
+                                                                (stat($self->UserConfigFile('Topics.txt')))[9],
+                                                                (stat($self->MainConfigFile('Languages.txt')))[9],
+                                                                (stat($self->UserConfigFile('Languages.txt')))[9] );
+
+    close(FH_CONFIGFILEINFO);
+    };
+
+
+#
+#   Function: LoadImageFileInfo
+#
+#   Loads the image file info from disk.
+#
+sub LoadImageFileInfo
+    {
+    my ($self) = @_;
+
+    my $version = NaturalDocs::BinaryFile->OpenForReading( NaturalDocs::Project->DataFile('ImageFileInfo.nd') );
+    my $fileIsOkay;
+
+    if (defined $version)
+        {
+        # It hasn't changed since being introduced.
+
+        if (NaturalDocs::Version->CheckFileFormat($version))
+            {  $fileIsOkay = 1;  }
+        else
+            {  NaturalDocs::BinaryFile->Close();  };
+        };
+
+    if ($fileIsOkay)
+        {
+        # [AString16: file name or undef]
+
+        while (my $imageFile = NaturalDocs::BinaryFile->GetAString16())
+            {
+            # [UInt32: last modified]
+            # [UInt8: was used]
+
+            my $lastModified = NaturalDocs::BinaryFile->GetUInt32();
+            my $wasUsed = NaturalDocs::BinaryFile->GetUInt8();
+
+            my $imageFileObject = $imageFiles{$imageFile};
+
+            # If there's an image file in ImageFileInfo.nd that no longer exists...
+            if (!$imageFileObject)
+                {
+                $imageFileObject = NaturalDocs::Project::ImageFile->New($lastModified, ::FILE_DOESNTEXIST(), $wasUsed);
+                $imageFiles{$imageFile} = $imageFileObject;
+
+                if ($wasUsed)
+                    {  $imageFilesToPurge{$imageFile} = 1;  };
+                }
+            else
+                {
+                $imageFileObject->SetWasUsed($wasUsed);
+
+                # This will be removed if it gets any references.
+                if ($wasUsed)
+                    {  $imageFilesToPurge{$imageFile} = 1;  };
+
+                if ($imageFileObject->LastModified() == $lastModified && !$rebuildEverything)
+                    {  $imageFileObject->SetStatus(::FILE_SAME());  }
+                else
+                    {  $imageFileObject->SetStatus(::FILE_CHANGED());  };
+                };
+            };
+
+        NaturalDocs::BinaryFile->Close();
+        }
+
+    else # !$fileIsOkay
+        {
+        $self->RebuildEverything();
+        };
+    };
+
+
+#
+#   Function: SaveImageFileInfo
+#
+#   Saves the image file info to disk.
+#
+sub SaveImageFileInfo
+    {
+    my $self = shift;
+
+    NaturalDocs::BinaryFile->OpenForWriting( NaturalDocs::Project->DataFile('ImageFileInfo.nd') );
+
+    while (my ($imageFile, $imageFileInfo) = each %imageFiles)
+        {
+        if ($imageFileInfo->Status() != ::FILE_DOESNTEXIST())
+            {
+            # [AString16: file name or undef]
+            # [UInt32: last modification time]
+            # [UInt8: was used]
+
+            NaturalDocs::BinaryFile->WriteAString16($imageFile);
+            NaturalDocs::BinaryFile->WriteUInt32($imageFileInfo->LastModified());
+            NaturalDocs::BinaryFile->WriteUInt8( ($imageFileInfo->ReferenceCount() > 0 ? 1 : 0) );
+            };
+        };
+
+    NaturalDocs::BinaryFile->WriteAString16(undef);
+    NaturalDocs::BinaryFile->Close();
+    };
+
+
+#
+#   Function: MigrateOldFiles
+#
+#   If the project uses the old file names used prior to 1.14, it converts them to the new file names.
+#
+sub MigrateOldFiles
+    {
+    my ($self) = @_;
+
+    my $projectDirectory = NaturalDocs::Settings->ProjectDirectory();
+
+    # We use the menu file as a test to see if we're using the new format.
+    if (-e NaturalDocs::File->JoinPaths($projectDirectory, 'NaturalDocs_Menu.txt'))
+        {
+        # The Data subdirectory would have been created by NaturalDocs::Settings.
+
+        rename( NaturalDocs::File->JoinPaths($projectDirectory, 'NaturalDocs_Menu.txt'), $self->UserConfigFile('Menu.txt') );
+
+        if (-e NaturalDocs::File->JoinPaths($projectDirectory, 'NaturalDocs.sym'))
+            {  rename( NaturalDocs::File->JoinPaths($projectDirectory, 'NaturalDocs.sym'), $self->DataFile('SymbolTable.nd') );  };
+
+        if (-e NaturalDocs::File->JoinPaths($projectDirectory, 'NaturalDocs.files'))
+            {  rename( NaturalDocs::File->JoinPaths($projectDirectory, 'NaturalDocs.files'), $self->DataFile('FileInfo.nd') );  };
+
+        if (-e NaturalDocs::File->JoinPaths($projectDirectory, 'NaturalDocs.m'))
+            {  rename( NaturalDocs::File->JoinPaths($projectDirectory, 'NaturalDocs.m'), $self->DataFile('PreviousMenuState.nd') );  };
+        };
+    };
+
+
+
+###############################################################################
+# Group: Config and Data File Functions
+
+
+#
+#   Function: MainConfigFile
+#
+#   Returns the full path to the passed main configuration file.  Pass the file name only.
+#
+sub MainConfigFile #(string file)
+    {
+    my ($self, $file) = @_;
+    return NaturalDocs::File->JoinPaths( NaturalDocs::Settings->ConfigDirectory(), $file );
+    };
+
+#
+#   Function: MainConfigFileStatus
+#
+#   Returns the <FileStatus> of the passed main configuration file.  Pass the file name only.
+#
+sub MainConfigFileStatus #(string file)
+    {
+    my ($self, $file) = @_;
+    return $mainConfigFiles{$file};
+    };
+
+#
+#   Function: UserConfigFile
+#
+#   Returns the full path to the passed user configuration file.  Pass the file name only.
+#
+sub UserConfigFile #(string file)
+    {
+    my ($self, $file) = @_;
+    return NaturalDocs::File->JoinPaths( NaturalDocs::Settings->ProjectDirectory(), $file );
+    };
+
+#
+#   Function: UserConfigFileStatus
+#
+#   Returns the <FileStatus> of the passed user configuration file.  Pass the file name only.
+#
+sub UserConfigFileStatus #(string file)
+    {
+    my ($self, $file) = @_;
+    return $userConfigFiles{$file};
+    };
+
+#
+#   Function: DataFile
+#
+#   Returns the full path to the passed data file.  Pass the file name only.
+#
+sub DataFile #(string file)
+    {
+    my ($self, $file) = @_;
+    return NaturalDocs::File->JoinPaths( NaturalDocs::Settings->ProjectDataDirectory(), $file );
+    };
+
+
+
+
+###############################################################################
+# Group: Source File Functions
+
+
+# Function: FilesToParse
+# Returns an existence hashref of the <FileNames> to parse.  This is not a copy of the data, so don't change it.
+sub FilesToParse
+    {  return \%filesToParse;  };
+
+# Function: FilesToBuild
+# Returns an existence hashref of the <FileNames> to build.  This is not a copy of the data, so don't change it.
+sub FilesToBuild
+    {  return \%filesToBuild;  };
+
+# Function: FilesToPurge
+# Returns an existence hashref of the <FileNames> that had content last time, but now either don't anymore or were deleted.
+# This is not a copy of the data, so don't change it.
+sub FilesToPurge
+    {  return \%filesToPurge;  };
+
+#
+#   Function: RebuildFile
+#
+#   Adds the file to the list of files to build.  This function will automatically filter out files that don't have Natural Docs content and
+#   files that are part of <FilesToPurge()>.  If this gets called on a file and that file later gets Natural Docs content, it will be added.
+#
+#   Parameters:
+#
+#       file - The <FileName> to build or rebuild.
+#
+sub RebuildFile #(file)
+    {
+    my ($self, $file) = @_;
+
+    # We don't want to add it to the build list if it doesn't exist, doesn't have Natural Docs content, or it's going to be purged.
+    # If it wasn't parsed yet and will later be found to have ND content, it will be added by SetHasContent().
+    if (exists $supportedFiles{$file} && !exists $filesToPurge{$file} && $supportedFiles{$file}->HasContent())
+        {
+        $filesToBuild{$file} = 1;
+
+        if (exists $unbuiltFilesWithContent{$file})
+            {  delete $unbuiltFilesWithContent{$file};  };
+        };
+    };
+
+
+#
+#   Function: ReparseEverything
+#
+#   Adds all supported files to the list of files to parse.  This does not necessarily mean these files are going to be rebuilt.
+#
+sub ReparseEverything
+    {
+    my ($self) = @_;
+
+    if (!$reparseEverything)
+        {
+        foreach my $file (keys %supportedFiles)
+            {
+            $filesToParse{$file} = 1;
+            };
+
+        $reparseEverything = 1;
+        };
+    };
+
+
+#
+#   Function: RebuildEverything
+#
+#   Adds all supported files to the list of files to build.  This does not necessarily mean these files are going to be reparsed.
+#
+sub RebuildEverything
+    {
+    my ($self) = @_;
+
+    foreach my $file (keys %unbuiltFilesWithContent)
+        {
+        $filesToBuild{$file} = 1;
+        };
+
+    %unbuiltFilesWithContent = ( );
+    $rebuildEverything = 1;
+
+    NaturalDocs::SymbolTable->RebuildAllIndexes();
+
+    if ($userConfigFiles{'Menu.txt'} == ::FILE_SAME())
+        {  $userConfigFiles{'Menu.txt'} = ::FILE_CHANGED();  };
+
+    while (my ($imageFile, $imageObject) = each %imageFiles)
+        {
+        if ($imageObject->ReferenceCount())
+            {  $imageFilesToUpdate{$imageFile} = 1;  };
+        };
+    };
+
+
+# Function: UnbuiltFilesWithContent
+# Returns an existence hashref of the <FileNames> that have Natural Docs content but are not part of <FilesToBuild()>.  This is
+# not a copy of the data so don't change it.
+sub UnbuiltFilesWithContent
+    {  return \%unbuiltFilesWithContent;  };
+
+# Function: FilesWithContent
+# Returns and existence hashref of the <FileNames> that have Natural Docs content.
+sub FilesWithContent
+    {
+    # Don't keep this one internally, but there's an easy way to make it.
+    return { %filesToBuild, %unbuiltFilesWithContent };
+    };
+
+
+#
+#   Function: HasContent
+#
+#   Returns whether the <FileName> contains Natural Docs content.
+#
+sub HasContent #(file)
+    {
+    my ($self, $file) = @_;
+
+    if (exists $supportedFiles{$file})
+        {  return $supportedFiles{$file}->HasContent();  }
+    else
+        {  return undef;  };
+    };
+
+
+#
+#   Function: SetHasContent
+#
+#   Sets whether the <FileName> has Natural Docs content or not.
+#
+sub SetHasContent #(file, hasContent)
+    {
+    my ($self, $file, $hasContent) = @_;
+
+    if (exists $supportedFiles{$file} && $supportedFiles{$file}->HasContent() != $hasContent)
+        {
+        # If the file now has content...
+        if ($hasContent)
+            {
+            $filesToBuild{$file} = 1;
+            }
+
+        # If the file's content has been removed...
+        else
+            {
+            delete $filesToBuild{$file};  # may not be there
+            $filesToPurge{$file} = 1;
+            };
+
+        $supportedFiles{$file}->SetHasContent($hasContent);
+        };
+    };
+
+
+#
+#   Function: StatusOf
+#
+#   Returns the <FileStatus> of the passed <FileName>.
+#
+sub StatusOf #(file)
+    {
+    my ($self, $file) = @_;
+
+    if (exists $supportedFiles{$file})
+        {  return $supportedFiles{$file}->Status();  }
+    else
+        {  return ::FILE_DOESNTEXIST();  };
+    };
+
+
+#
+#   Function: DefaultMenuTitleOf
+#
+#   Returns the default menu title of the <FileName>.  If one isn't specified, it returns the <FileName>.
+#
+sub DefaultMenuTitleOf #(file)
+    {
+    my ($self, $file) = @_;
+
+    if (exists $supportedFiles{$file})
+        {  return $supportedFiles{$file}->DefaultMenuTitle();  }
+    else
+        {  return $file;  };
+    };
+
+
+#
+#   Function: SetDefaultMenuTitle
+#
+#   Sets the <FileName's> default menu title.
+#
+sub SetDefaultMenuTitle #(file, menuTitle)
+    {
+    my ($self, $file, $menuTitle) = @_;
+
+    if (exists $supportedFiles{$file} && $supportedFiles{$file}->DefaultMenuTitle() ne $menuTitle)
+        {
+        $supportedFiles{$file}->SetDefaultMenuTitle($menuTitle);
+        NaturalDocs::Menu->OnDefaultTitleChange($file);
+        };
+    };
+
+
+#
+#   Function: MostUsedLanguage
+#
+#   Returns the name of the most used language in the source trees.  Does not include text files.
+#
+sub MostUsedLanguage
+    {  return $mostUsedLanguage;  };
+
+
+
+
+###############################################################################
+# Group: Image File Functions
+
+
+#
+#   Function: ImageFileExists
+#   Returns whether the passed image file exists.
+#
+sub ImageFileExists #(FileName file) => bool
+    {
+    my ($self, $file) = @_;
+
+    if (!exists $imageFiles{$file})
+        {  $file = $insensitiveImageFiles{lc($file)};  };
+
+    return (exists $imageFiles{$file} && $imageFiles{$file}->Status() != ::FILE_DOESNTEXIST());
+    };
+
+
+#
+#   Function: ImageFileDimensions
+#   Returns the dimensions of the passed image file as the array ( width, height ).  Returns them both as undef if it cannot be
+#   determined.
+#
+sub ImageFileDimensions #(FileName file) => (int, int)
+    {
+    my ($self, $file) = @_;
+
+    if (!exists $imageFiles{$file})
+        {  $file = $insensitiveImageFiles{lc($file)};  };
+
+    my $object = $imageFiles{$file};
+    if (!$object)
+        {  die "Tried to get the dimensions of an image that doesn't exist.";  };
+
+    if ($object->Width() == -1)
+        {  $self->DetermineImageDimensions($file);  };
+
+    return ($object->Width(), $object->Height());
+    };
+
+
+#
+#   Function: ImageFileCapitalization
+#   Returns the properly capitalized version of the passed image <FileName>.  Image file paths are treated as case insensitive
+#   regardless of whether the underlying operating system is or not, so we have to make sure the final version matches the
+#   capitalization of the actual file.
+#
+sub ImageFileCapitalization #(FileName file) => FileName
+    {
+    my ($self, $file) = @_;
+
+    if (exists $imageFiles{$file})
+        {  return $file;  }
+    elsif (exists $insensitiveImageFiles{lc($file)})
+        {  return $insensitiveImageFiles{lc($file)};  }
+    else
+        {  die "Tried to get the capitalization of an image file that doesn't exist.";  };
+    };
+
+
+#
+#   Function: AddImageFileReference
+#   Adds a reference to the passed image <FileName>.
+#
+sub AddImageFileReference #(FileName imageFile)
+    {
+    my ($self, $imageFile) = @_;
+
+    if (!exists $imageFiles{$imageFile})
+        {  $imageFile = $insensitiveImageFiles{lc($imageFile)};  };
+
+    my $imageFileInfo = $imageFiles{$imageFile};
+
+    if ($imageFileInfo == undef || $imageFileInfo->Status() == ::FILE_DOESNTEXIST())
+        {  die "Tried to add a reference to a non-existant image file.";  };
+
+    if ($imageFileInfo->AddReference() == 1)
+        {
+        delete $imageFilesToPurge{$imageFile};
+
+        if (!$imageFileInfo->WasUsed() ||
+            $imageFileInfo->Status() == ::FILE_NEW() ||
+            $imageFileInfo->Status() == ::FILE_CHANGED())
+            {  $imageFilesToUpdate{$imageFile} = 1;  };
+        };
+    };
+
+
+#
+#   Function: DeleteImageFileReference
+#   Deletes a reference from the passed image <FileName>.
+#
+sub DeleteImageFileReference #(FileName imageFile)
+    {
+    my ($self, $imageFile) = @_;
+
+    if (!exists $imageFiles{$imageFile})
+        {  $imageFile = $insensitiveImageFiles{lc($imageFile)};  };
+
+    if (!exists $imageFiles{$imageFile})
+        {  die "Tried to delete a reference to a non-existant image file.";  };
+
+    if ($imageFiles{$imageFile}->DeleteReference() == 0)
+        {
+        delete $imageFilesToUpdate{$imageFile};
+
+        if ($imageFiles{$imageFile}->WasUsed())
+            {  $imageFilesToPurge{$imageFile} = 1;  };
+        };
+    };
+
+
+#
+#   Function: ImageFilesToUpdate
+#   Returns an existence hashref of image <FileNames> that need to be updated.  *Do not change.*
+#
+sub ImageFilesToUpdate
+    {  return \%imageFilesToUpdate;  };
+
+
+#
+#   Function: ImageFilesToPurge
+#   Returns an existence hashref of image <FileNames> that need to be updated.  *Do not change.*
+#
+sub ImageFilesToPurge
+    {  return \%imageFilesToPurge;  };
+
+
+
+###############################################################################
+# Group: Support Functions
+
+#
+#   Function: GetAllSupportedFiles
+#
+#   Gets all the supported files in the passed directory and its subdirectories and puts them into <supportedFiles>.  The only
+#   attribute that will be set is <NaturalDocs::Project::SourceFile->LastModified()>.  Also sets <mostUsedLanguage>.
+#
+sub GetAllSupportedFiles
+    {
+    my ($self) = @_;
+
+    my @directories = @{NaturalDocs::Settings->InputDirectories()};
+    my $isCaseSensitive = NaturalDocs::File->IsCaseSensitive();
+
+    # Keys are language names, values are counts.
+    my %languageCounts;
+
+
+    # Make an existence hash of excluded directories.
+
+    my %excludedDirectories;
+    my $excludedDirectoryArrayRef = NaturalDocs::Settings->ExcludedInputDirectories();
+
+    foreach my $excludedDirectory (@$excludedDirectoryArrayRef)
+        {
+        if ($isCaseSensitive)
+            {  $excludedDirectories{$excludedDirectory} = 1;  }
+        else
+            {  $excludedDirectories{lc($excludedDirectory)} = 1;  };
+        };
+
+
+    my $imagesOnly;
+    my $language;
+
+    while (scalar @directories)
+        {
+        my $directory = pop @directories;
+
+        opendir DIRECTORYHANDLE, $directory;
+        my @entries = readdir DIRECTORYHANDLE;
+        closedir DIRECTORYHANDLE;
+
+        @entries = NaturalDocs::File->NoUpwards(@entries);
+
+        foreach my $entry (@entries)
+            {
+            my $fullEntry = NaturalDocs::File->JoinPaths($directory, $entry);
+
+            # If an entry is a directory, recurse.
+            if (-d $fullEntry)
+                {
+                # Join again with the noFile flag set in case the platform handles them differently.
+                $fullEntry = NaturalDocs::File->JoinPaths($directory, $entry, 1);
+
+                if ($isCaseSensitive)
+                    {
+                    if (!exists $excludedDirectories{$fullEntry})
+                        {  push @directories, $fullEntry;  };
+                    }
+                else
+                    {
+                    if (!exists $excludedDirectories{lc($fullEntry)})
+                        {  push @directories, $fullEntry;  };
+                    };
+                }
+
+            # Otherwise add it if it's a supported extension.
+            else
+                {
+                my $extension = NaturalDocs::File->ExtensionOf($entry);
+
+                if (exists $imageFileExtensions{lc($extension)})
+                    {
+                    my $fileObject = NaturalDocs::Project::ImageFile->New( (stat($fullEntry))[9], ::FILE_NEW(), 0 );
+                    $imageFiles{$fullEntry} = $fileObject;
+
+                    my $lcFullEntry = lc($fullEntry);
+
+                    if (!exists $insensitiveImageFiles{$lcFullEntry} ||
+                        ($fullEntry cmp $insensitiveImageFiles{$lcFullEntry}) < 0)
+                        {
+                        $insensitiveImageFiles{$lcFullEntry} = $fullEntry;
+                        };
+                    }
+                elsif (!$imagesOnly && ($language = NaturalDocs::Languages->LanguageOf($fullEntry)) )
+                    {
+                    my $fileObject = NaturalDocs::Project::SourceFile->New();
+                    $fileObject->SetLastModified(( stat($fullEntry))[9] );
+                    $supportedFiles{$fullEntry} = $fileObject;
+
+                    $languageCounts{$language->Name()}++;
+                    };
+                };
+            };
+
+
+        # After we run out of source directories, add the image directories.
+
+        if (scalar @directories == 0 && !$imagesOnly)
+            {
+            $imagesOnly = 1;
+            @directories = @{NaturalDocs::Settings->ImageDirectories()};
+            };
+        };
+
+
+    my $topCount = 0;
+
+    while (my ($language, $count) = each %languageCounts)
+        {
+        if ($count > $topCount && $language ne 'Text File')
+            {
+            $topCount = $count;
+            $mostUsedLanguage = $language;
+            };
+        };
+    };
+
+
+#
+#   Function: DetermineImageDimensions
+#
+#   Attempts to determine the dimensions of the passed image and apply them to their object in <imageFiles>.  Will set them to
+#   undef if they can't be determined.
+#
+sub DetermineImageDimensions #(FileName imageFile)
+    {
+    my ($self, $imageFile) = @_;
+
+    my $imageFileObject = $imageFiles{$imageFile};
+    if (!defined $imageFileObject)
+        {  die "Tried to determine image dimensions of a file with no object.";  };
+
+    my $extension = lc( NaturalDocs::File->ExtensionOf($imageFile) );
+    my ($width, $height);
+
+    if ($imageFileExtensions{$extension})
+        {
+        open(FH_IMAGEFILE, '<' . $imageFile)
+            or die 'Could not open ' . $imageFile . "\n";
+        binmode(FH_IMAGEFILE);
+
+        my $raw;
+
+        if ($extension eq 'gif')
+            {
+            read(FH_IMAGEFILE, $raw, 6);
+
+            if ($raw eq 'GIF87a' || $raw eq 'GIF89a')
+                {
+                read(FH_IMAGEFILE, $raw, 4);
+                ($width, $height) = unpack('vv', $raw);
+                };
+            }
+
+        elsif ($extension eq 'png')
+            {
+            read(FH_IMAGEFILE, $raw, 8);
+
+            if ($raw eq "\x89PNG\x0D\x0A\x1A\x0A")
+                {
+                seek(FH_IMAGEFILE, 4, 1);
+                read(FH_IMAGEFILE, $raw, 4);
+
+                if ($raw eq 'IHDR')
+                    {
+                    read(FH_IMAGEFILE, $raw, 8);
+                    ($width, $height) = unpack('NN', $raw);
+                    };
+                };
+            }
+
+        elsif ($extension eq 'bmp')
+            {
+            read(FH_IMAGEFILE, $raw, 2);
+
+            if ($raw eq 'BM')
+                {
+                seek(FH_IMAGEFILE, 16, 1);
+                read(FH_IMAGEFILE, $raw, 8);
+
+                ($width, $height) = unpack('VV', $raw);
+                };
+            }
+
+        elsif ($extension eq 'jpg' || $extension eq 'jpeg')
+            {
+            read(FH_IMAGEFILE, $raw, 2);
+            my $isOkay = ($raw eq "\xFF\xD8");
+
+            while ($isOkay)
+                {
+                read(FH_IMAGEFILE, $raw, 4);
+                my ($marker, $code, $length) = unpack('CCn', $raw);
+
+                $isOkay = ($marker eq 0xFF);
+
+                if ($isOkay)
+                    {
+                    if ($code >= 0xC0 && $code <= 0xC3)
+                        {
+                        read(FH_IMAGEFILE, $raw, 5);
+                        ($height, $width) = unpack('xnn', $raw);
+                        last;
+                        }
+
+                    else
+                        {
+                        $isOkay = seek(FH_IMAGEFILE, $length - 2, 1);
+                        };
+                    };
+                };
+            };
+
+        close(FH_IMAGEFILE);
+        };
+
+
+    # Sanity check the values.  Although images can theoretically be bigger than 5000, most won't.  The worst that happens in this
+    # case is just that they don't get length and width values in the output anyway.
+    if ($width > 0 && $width < 5000 && $height > 0 && $height < 5000)
+        {  $imageFileObject->SetDimensions($width, $height);  }
+    else
+        {  $imageFileObject->SetDimensions(undef, undef);  };
+    };
+
+
+1;

Added: trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/ReferenceString.pm
===================================================================
--- trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/ReferenceString.pm	                        (rev 0)
+++ trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/ReferenceString.pm	2009-07-01 15:30:00 UTC (rev 4275)
@@ -0,0 +1,334 @@
+###############################################################################
+#
+#   Package: NaturalDocs::ReferenceString
+#
+###############################################################################
+#
+#   A package to manage <ReferenceString> handling throughout the program.
+#
+###############################################################################
+
+# 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::ReferenceString;
+
+use vars '@ISA', '@EXPORT';
+ at ISA = 'Exporter';
+ at EXPORT = ( 'BINARYREF_NOTYPE', 'BINARYREF_NORESOLVINGFLAGS',
+
+                     'REFERENCE_TEXT', 'REFERENCE_CH_CLASS', 'REFERENCE_CH_PARENT',
+
+                     'RESOLVE_RELATIVE', 'RESOLVE_ABSOLUTE', 'RESOLVE_NOPLURAL', 'RESOLVE_NOUSING' );
+
+
+#
+#   Constants: Binary Format Flags
+#
+#   These flags can be combined to specify the format when using <ToBinaryFile()> and <FromBinaryFile()>.  All are exported
+#   by default.
+#
+#   BINARYREF_NOTYPE - Do not include the <ReferenceType>.
+#   BINARYREF_NORESOLVEFLAGS - Do not include the <Resolving Flags>.
+#
+use constant BINARYREF_NOTYPE => 0x01;
+use constant BINARYREF_NORESOLVINGFLAGS => 0x02;
+
+
+#
+#   Constants: ReferenceType
+#
+#   The type of a reference.
+#
+#       REFERENCE_TEXT - The reference appears in the text of the documentation.
+#       REFERENCE_CH_CLASS - A class reference handled by <NaturalDocs::ClassHierarchy>.
+#       REFERENCE_CH_PARENT - A parent class reference handled by <NaturalDocs::ClassHierarchy>.
+#
+#   Dependencies:
+#
+#       - <ToBinaryFile()> and <FromBinaryFile()> require that these values fit into a UInt8, i.e. are <= 255.
+#
+use constant REFERENCE_TEXT => 1;
+use constant REFERENCE_CH_CLASS => 2;
+use constant REFERENCE_CH_PARENT => 3;
+
+
+#
+#   Constants: Resolving Flags
+#
+#   Used to influence the method of resolving references in <NaturalDocs::SymbolTable>.
+#
+#       RESOLVE_RELATIVE - The reference text is truly relative, rather than Natural Docs' semi-relative.
+#       RESOLVE_ABSOLUTE - The reference text is always absolute.  No local or relative references.
+#       RESOLVE_NOPLURAL - The reference text may not be interpreted as a plural, and thus match singular forms as well.
+#       RESOLVE_NOUSING - The reference text may not include "using" statements when being resolved.
+#
+#       If neither <RESOLVE_RELATIVE> or <RESOLVE_ABSOLUTE> is specified, Natural Docs' semi-relative kicks in instead,
+#       which is where links are interpreted as local, then global, then relative.  <RESOLVE_RELATIVE> states that links are
+#       local, then relative, then global.
+#
+#   Dependencies:
+#
+#       - <ToBinaryFile()> and <FromBinaryFile()> require that these values fit into a UInt8, i.e. are <= 255.
+#
+use constant RESOLVE_RELATIVE => 0x01;
+use constant RESOLVE_ABSOLUTE => 0x02;
+use constant RESOLVE_NOPLURAL => 0x04;
+use constant RESOLVE_NOUSING => 0x08;
+
+
+#
+#
+#   Function: MakeFrom
+#
+#   Encodes the passed information as a <ReferenceString>.  The format of the string should be treated as opaque.  However, the
+#   characteristic you can rely on is that the same string will always be made from the same parameters, and thus it's suitable
+#   for comparison and use as hash keys.
+#
+#   Parameters:
+#
+#       type - The <ReferenceType>.
+#       symbol - The <SymbolString> of the reference.
+#       language - The name of the language that defines the file this reference appears in.
+#       scope - The scope <SymbolString> the reference appears in, or undef if none.
+#       using - An arrayref of scope <SymbolStrings> that are also available for checking due to the equivalent a "using" statement,
+#                  or undef if none.
+#       resolvingFlags - The <Resolving Flags> to use with this reference.  They are ignored if the type is <REFERENCE_TEXT>.
+#
+#   Returns:
+#
+#       The encoded <ReferenceString>.
+#
+sub MakeFrom #(ReferenceType type, SymbolString symbol, string language, SymbolString scope, SymbolString[]* using, flags resolvingFlags)
+    {
+    my ($self, $type, $symbol, $language, $scope, $using, $resolvingFlags) = @_;
+
+    if ($type == ::REFERENCE_TEXT() || $resolvingFlags == 0)
+       {  $resolvingFlags = undef;  };
+
+    # The format is [type] 0x1E [resolving flags] 0x1E [symbol] 0x1E [scope] ( 0x1E [using] )*
+    # If there is no scope and/or using, the separator characters still remain.
+
+    # DEPENDENCY: SymbolString->FromText() removed all 0x1E characters.
+    # DEPENDENCY: SymbolString->FromText() doesn't use 0x1E characters in its encoding.
+
+    my $string = $type . "\x1E" . $symbol . "\x1E" . $language . "\x1E" . $resolvingFlags . "\x1E";
+
+    if (defined $scope)
+        {
+        $string .= $scope;
+        };
+
+    $string .= "\x1E";
+
+    if (defined $using)
+        {
+        $string .= join("\x1E", @$using);
+        };
+
+    return $string;
+    };
+
+
+#
+#   Function: ToBinaryFile
+#
+#   Writes a <ReferenceString> to the passed filehandle.  Can also encode an undef.
+#
+#   Parameters:
+#
+#       fileHandle - The filehandle to write to.
+#       referenceString - The <ReferenceString> to write, or undef.
+#       binaryFormatFlags - Any <Binary Format Flags> you want to use to influence encoding.
+#
+#   Format:
+#
+#       > [SymbolString: Symbol or undef for an undef reference]
+#       > [AString16: language]
+#       > [SymbolString: Scope or undef for none]
+#       >
+#       > [SymbolString: Using or undef for none]
+#       > [SymbolString: Using or undef for no more]
+#       > ...
+#       >
+#       > [UInt8: Type unless BINARYREF_NOTYPE is set]
+#       > [UInt8: Resolving Flags unless BINARYREF_NORESOLVINGFLAGS is set]
+#
+#   Dependencies:
+#
+#       - <ReferenceTypes> must fit into a UInt8.  All values must be <= 255.
+#       - All <Resolving Flags> must fit into a UInt8.  All values must be <= 255.
+#
+sub ToBinaryFile #(FileHandle fileHandle, ReferenceString referenceString, flags binaryFormatFlags)
+    {
+    my ($self, $fileHandle, $referenceString, $binaryFormatFlags) = @_;
+
+    my ($type, $symbol, $language, $scope, $using, $resolvingFlags) = $self->InformationOf($referenceString);
+
+    # [SymbolString: Symbol or undef for an undef reference]
+
+    NaturalDocs::SymbolString->ToBinaryFile($fileHandle, $symbol);
+
+    # [AString16: language]
+
+    print $fileHandle pack('nA*', length $language, $language);
+
+    # [SymbolString: scope or undef if none]
+
+    NaturalDocs::SymbolString->ToBinaryFile($fileHandle, $scope);
+
+    # [SymbolString: using or undef if none/no more] ...
+
+    if (defined $using)
+        {
+        foreach my $usingScope (@$using)
+            {  NaturalDocs::SymbolString->ToBinaryFile($fileHandle, $usingScope);  };
+        };
+
+    NaturalDocs::SymbolString->ToBinaryFile($fileHandle, undef);
+
+    # [UInt8: Type unless BINARYREF_NOTYPE is set]
+
+    if (!($binaryFormatFlags & BINARYREF_NOTYPE))
+        {  print $fileHandle pack('C', $type);  };
+
+    # [UInt8: Resolving Flags unless BINARYREF_NORESOLVINGFLAGS is set]
+
+    if (!($binaryFormatFlags & BINARYREF_NORESOLVINGFLAGS))
+        {  print $fileHandle pack('C', $type);  };
+    };
+
+
+#
+#   Function: FromBinaryFile
+#
+#   Reads a <ReferenceString> or undef from the passed filehandle.
+#
+#   Parameters:
+#
+#       fileHandle - The filehandle to read from.
+#       binaryFormatFlags - Any <Binary Format Flags> you want to use to influence decoding.
+#       type - The <ReferenceType> to use if <BINARYREF_NOTYPE> is set.
+#       resolvingFlags - The <Resolving Flags> to use if <BINARYREF_NORESOLVINGFLAGS> is set.
+#
+#   Returns:
+#
+#       The <ReferenceString> or undef.
+#
+#   See Also:
+#
+#       See <ToBinaryFile()> for format and dependencies.
+#
+sub FromBinaryFile #(FileHandle fileHandle, flags binaryFormatFlags, ReferenceType type, flags resolvingFlags)
+    {
+    my ($self, $fileHandle, $binaryFormatFlags, $type, $resolvingFlags) = @_;
+    my $raw;
+
+    # [SymbolString: Symbol or undef for an undef reference]
+
+    my $symbol = NaturalDocs::SymbolString->FromBinaryFile($fileHandle);
+
+    if (!defined $symbol)
+        {  return undef;  };
+
+
+    # [AString16: language]
+
+    read($fileHandle, $raw, 2);
+    my $languageLength = unpack('n', $raw);
+
+    my $language;
+    read($fileHandle, $language, $languageLength);
+
+
+    # [SymbolString: scope or undef if none]
+
+    my $scope = NaturalDocs::SymbolString->FromBinaryFile($fileHandle);
+
+    # [SymbolString: using or undef if none/no more] ...
+
+    my $usingSymbol;
+    my @using;
+
+    while ($usingSymbol = NaturalDocs::SymbolString->FromBinaryFile($fileHandle))
+        {  push @using, $usingSymbol;  };
+
+    if (scalar @using)
+        {  $usingSymbol = \@using;  }
+    else
+        {  $usingSymbol = undef;  };
+
+    # [UInt8: Type unless BINARYREF_NOTYPE is set]
+
+    if (!($binaryFormatFlags & BINARYREF_NOTYPE))
+        {
+        my $raw;
+        read($fileHandle, $raw, 1);
+        $type = unpack('C', $raw);
+        };
+
+    # [UInt8: Resolving Flags unless BINARYREF_NORESOLVINGFLAGS is set]
+
+    if (!($binaryFormatFlags & BINARYREF_NORESOLVINGFLAGS))
+        {
+        my $raw;
+        read($fileHandle, $raw, 1);
+        $resolvingFlags = unpack('C', $raw);
+        };
+
+    return $self->MakeFrom($type, $symbol, $language, $scope, $usingSymbol, $resolvingFlags);
+    };
+
+
+#
+#   Function: InformationOf
+#
+#   Returns the information encoded in a <ReferenceString>.
+#
+#   Parameters:
+#
+#       referenceString - The <ReferenceString> to decode.
+#
+#   Returns:
+#
+#       The array ( type, symbol, language, scope, using, resolvingFlags ).
+#
+#       type - The <ReferenceType>.
+#       symbol - The <SymbolString>.
+#       language - The name of the language that defined the file the reference was defined in.
+#       scope - The scope <SymbolString>, or undef if none.
+#       using - An arrayref of scope <SymbolStrings> that the reference also has access to via "using" statements, or undef if none.
+#       resolvingFlags - The <Resolving Flags> of the reference.
+#
+sub InformationOf #(ReferenceString referenceString)
+    {
+    my ($self, $referenceString) = @_;
+
+    my ($type, $symbolString, $language, $resolvingFlags, $scopeString, @usingStrings) = split(/\x1E/, $referenceString);
+
+    if (!length $resolvingFlags)
+        {  $resolvingFlags = undef;  };
+
+    return ( $type, $symbolString, $language, $scopeString, [ @usingStrings ], $resolvingFlags );
+    };
+
+
+#
+#   Function: TypeOf
+#
+#   Returns the <ReferenceType> encoded in the reference string.  This is faster than <InformationOf()> if this is
+#   the only information you need.
+#
+sub TypeOf #(ReferenceString referenceString)
+    {
+    my ($self, $referenceString) = @_;
+
+    $referenceString =~ /^([^\x1E]+)/;
+    return $1;
+    };
+
+
+1;

Added: trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Settings/BuildTarget.pm
===================================================================
--- trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Settings/BuildTarget.pm	                        (rev 0)
+++ trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Settings/BuildTarget.pm	2009-07-01 15:30:00 UTC (rev 4275)
@@ -0,0 +1,66 @@
+###############################################################################
+#
+#   Class: NaturalDocs::Settings::BuildTarget
+#
+###############################################################################
+#
+#   A class that stores information about a build target.
+#
+###############################################################################
+
+# 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::Settings::BuildTarget;
+
+use NaturalDocs::DefineMembers 'BUILDER', 'Builder()', 'SetBuilder()',
+                                                 'DIRECTORY', 'Directory()', 'SetDirectory()';
+
+
+#
+#   Constants: Members
+#
+#   The class is implemented as a blessed arrayref with the members below.
+#
+#       BUILDER      - The <NaturalDocs::Builder::Base>-derived object for the target's output format.
+#       DIRECTORY - The output directory of the target.
+#
+
+#
+#   Function: New
+#
+#   Creates and returns a new object.
+#
+#   Parameters:
+#
+#       builder - The <NaturalDocs::Builder::Base>-derived object for the target's output format.
+#       directory - The directory to place the output files in.
+#
+sub New #(builder, directory)
+    {
+    my ($package, $builder, $directory) = @_;
+
+    my $object = [ ];
+    bless $object, $package;
+
+    $object->SetBuilder($builder);
+    $object->SetDirectory($directory);
+
+    return $object;
+    };
+
+
+#
+#   Functions: Member Functions
+#
+#   Builder - Returns the <NaturalDocs::Builder::Base>-derived object for the target's output format.
+#   SetBuilder - Replaces the <NaturalDocs::Builder::Base>-derived object for the target's output format.
+#   Directory - Returns the directory for the target's output files.
+#   SetDirectory - Replaces the directory for the target's output files.
+#
+
+
+1;

Added: trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Settings.pm
===================================================================
--- trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Settings.pm	                        (rev 0)
+++ trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Settings.pm	2009-07-01 15:30:00 UTC (rev 4275)
@@ -0,0 +1,1418 @@
+###############################################################################
+#
+#   Package: NaturalDocs::Settings
+#
+###############################################################################
+#
+#   A package to handle the command line and various other program settings.
+#
+###############################################################################
+
+# This file is part of Natural Docs, which is Copyright (C) 2003-2008 Greg Valure
+# Natural Docs is licensed under the GPL
+
+use Cwd ();
+
+use NaturalDocs::Settings::BuildTarget;
+
+use strict;
+use integer;
+
+package NaturalDocs::Settings;
+
+
+###############################################################################
+# Group: Information
+
+=pod begin nd
+
+    Topic: Usage and Dependencies
+
+        - The <Constant Functions> can be called immediately.
+
+        - Prior to initialization, <NaturalDocs::Builder> must have all its output packages registered.
+
+        - To initialize, call <Load()>.  All functions except <InputDirectoryNameOf()> will then be available.
+
+        - <GenerateDirectoryNames()> must be called before <InputDirectoryNameOf()> will work.  Currently it is called by
+          <NaturalDocs::Menu->LoadAndUpdate()>.
+
+
+    Architecture: Internal Overview
+
+        - <Load()> first parses the command line, gathering all the settings and checking for errors.  All <NaturalDocs::Builder>
+          packages must be registered before this is called because it needs their command line options.
+          <NaturalDocs::Project->ReparseEverything()> and <NaturalDocs::Project->RebuildEverything()> are called right away if -r
+          or -ro are used.
+
+        - Output directories are *not* named at this point.  See <Named Directories>.
+
+        - The previous settings from the last time Natural Docs was run are loaded and compared to the current settings.
+          <NaturalDocs::Project->ReparseEverything()> and <NaturalDocs::Project->RebuildEverything()> are called if there are
+          any differences that warrant it.
+
+        - It then waits for <GenerateDirectoryNames()> to be called by <NaturalDocs::Menu>.  The reason for this is that the
+          previous directory names are stored as hints in the menu file, for reasons explained in <Named Directories>.  Once that
+          happens all the unnamed directories have names generated for them so everything is named.  The package is completely
+          set up.
+
+        - The input directories are stored in an array instead of a hash because the order they were declared in matters.  If two
+          people use multiple input directories on separate computers without sharing a menu file, they should at least get consistent
+          directory names by declaring them in the same order.
+
+
+    Architecture: Named Directories
+
+        Ever since Natural Docs introduced multiple input directories in 1.16, they've had to be named.  Since they don't necessarily
+        extend from the same root anymore, they can't share an output directory without the risk of file name conflicts.  There was
+        an early attempt at giving them actual names, but now they're just numbered from 1.
+
+        Directory names aren't generated right away.  It waits for <Menu.txt> to load because that holds the obfuscated names from
+        the last run.  <NaturalDocs::Menu> then calls <GenerateDirectoryNames()> and passes those along as hints.
+        <GenerateDirectoryNames()> then applies them to any matches and generates new ones for any remaining.  This is done so
+        that output page locations can remain consistent when built on multiple computers, so long as the menu file is shared.  I tend
+        to think the menu file is the most likely configuration file to be shared.
+
+
+    Architecture: Removed Directories
+
+        Directories that were part of the previous run but aren't anymore are still stored in the package.  The primary reason, though
+        there may be others, is file purging.  If an input directory is removed, all the output files that were generated from anything
+        in it need to be removed.  To find out what the output file name was for a removed source file, it needs to be able to split it
+        from it's original input directory and know what that directory was named.  If this didn't happen those output files would be
+        orphaned, as was the case prior to 1.32.
+
+=cut
+
+
+
+###############################################################################
+# Group: Variables
+
+
+# handle: PREVIOUS_SETTINGS_FILEHANDLE
+# The file handle used with <PreviousSettings.nd>.
+
+# array: inputDirectories
+# An array of input directories.
+my @inputDirectories;
+
+# array: inputDirectoryNames
+# An array of the input directory names.  Each name corresponds to the directory of the same index in <inputDirectories>.
+my @inputDirectoryNames;
+
+# array: imageDirectories
+# An array of image directories.
+my @imageDirectories;
+
+# array: imageDirectoryNames
+# An array of the image directory names.  Each name corresponds to the directory of the same index in <imageDirectories>.
+my @imageDirectoryNames;
+
+# array: relativeImageDirectories
+# An array of the relative paths for images.  The asterisks found in the command line are not present.
+my @relativeImageDirectories;
+
+# array: excludedInputDirectories
+# An array of input directories to exclude.
+my @excludedInputDirectories;
+
+# array: removedInputDirectories
+# An array of input directories that were once in the command line but are no longer.
+my @removedInputDirectories;
+
+# array: removedInputDirectoryNames
+# An array of the removed input directories' names.  Each name corresponds to the directory of the same index in
+# <removedInputDirectories>.
+my @removedInputDirectoryNames;
+
+# array: removedImageDirectories
+# An array of image directories that were once in the command line but are no longer.
+my @removedImageDirectories;
+
+# array: removedImageDirectoryNames
+# An array of the removed image directories' names.  Each name corresponds to the directory of the same index in
+# <removedImageDirectories>.
+my @removedImageDirectoryNames;
+
+# var: projectDirectory
+# The project directory.
+my $projectDirectory;
+
+# array: buildTargets
+# An array of <NaturalDocs::Settings::BuildTarget>s.
+my @buildTargets;
+
+# var: documentedOnly
+# Whether undocumented code aspects should be included in the output.
+my $documentedOnly;
+
+# int: tabLength
+# The number of spaces in tabs.
+my $tabLength;
+
+# bool: noAutoGroup
+# Whether auto-grouping is turned off.
+my $noAutoGroup;
+
+# bool: onlyFileTitles
+# Whether source files should always use the file name as the title.
+my $onlyFileTitles;
+
+# bool: isQuiet
+# Whether the script should be run in quiet mode or not.
+my $isQuiet;
+
+# bool: rebuildData
+# WHether most data files should be ignored and rebuilt.
+my $rebuildData;
+
+# array: styles
+# An array of style names to use, most important first.
+my @styles;
+
+# var: charset
+# The character encoding of the source files, and thus the output.
+my $charset;
+
+
+###############################################################################
+# Group: Files
+
+
+#
+#   File: PreviousSettings.nd
+#
+#   Stores the previous command line settings.
+#
+#   Format:
+#
+#       > [BINARY_FORMAT]
+#       > [VersionInt: app version]
+#
+#       The file starts with the standard <BINARY_FORMAT> <VersionInt> header.
+#
+#       > [UInt8: tab length]
+#       > [UInt8: documented only (0 or 1)]
+#       > [UInt8: no auto-group (0 or 1)]
+#       > [UInt8: only file titles (0 or 1)]
+#       > [AString16: charset]
+#       >
+#       > [UInt8: number of input directories]
+#       > [AString16: input directory] [AString16: input directory name] ...
+#
+#       A count of input directories, then that number of directory/name pairs.
+#
+#       > [UInt8: number of output targets]
+#       > [AString16: output directory] [AString16: output format command line option] ...
+#
+#       A count of output targets, then that number of directory/format pairs.
+#
+#
+#   Revisions:
+#
+#       1.4:
+#
+#           - Added only file titles.
+#
+#       1.33:
+#
+#           - Added charset.
+#
+#       1.3:
+#
+#           - Removed headers-only, which was a 0/1 UInt8 after tab length.
+#           - Change auto-group level (1 = no, 2 = yes, 3 = full only) to no auto-group (0 or 1).
+#
+#       1.22:
+#
+#           - Added auto-group level.
+#
+#       1.2:
+#
+#           - File was added to the project.  Prior to 1.2, it didn't exist.
+#
+
+
+###############################################################################
+# Group: Action Functions
+
+#
+#   Function: Load
+#
+#   Loads and parses all settings from the command line and configuration files.  Will exit if the options are invalid or the syntax
+#   reference was requested.
+#
+sub Load
+    {
+    my ($self) = @_;
+
+    $self->ParseCommandLine();
+    $self->LoadAndComparePreviousSettings();
+    };
+
+
+#
+#   Function: Save
+#
+#   Saves all settings in configuration files to disk.
+#
+sub Save
+    {
+    my ($self) = @_;
+
+    $self->SavePreviousSettings();
+    };
+
+
+#
+#   Function: GenerateDirectoryNames
+#
+#   Generates names for each of the input and image directories, which can later be retrieved with <InputDirectoryNameOf()>
+#   and <ImageDirectoryNameOf()>.
+#
+#   Parameters:
+#
+#       inputHints - A hashref of suggested input directory names, where the keys are the directories and the values are the names.
+#                        These take precedence over anything generated.  You should include names for directories that are no longer in
+#                        the command line.  This parameter may be undef.
+#       imageHints - Same as inputHints, only for the image directories.
+#
+sub GenerateDirectoryNames #(hashref inputHints, hashref imageHints)
+    {
+    my ($self, $inputHints, $imageHints) = @_;
+
+    my %usedInputNames;
+    my %usedImageNames;
+
+
+    if (defined $inputHints)
+        {
+        # First, we have to convert all non-numeric names to numbers, since they may come from a pre-1.32 menu file.  We do it
+        # here instead of in NaturalDocs::Menu to keep the naming scheme centralized.
+
+        my @names = values %$inputHints;
+        my $hasNonNumeric;
+
+        foreach my $name (@names)
+            {
+            if ($name !~ /^[0-9]+$/)
+                {
+                $hasNonNumeric = 1;
+                last;
+                };
+            };
+
+
+        if ($hasNonNumeric)
+            {
+            # Hash mapping old names to new names.
+            my %conversion;
+
+            # The sequential number to use.  Starts at two because we want 'default' to be one.
+            my $currentNumber = 2;
+
+            # If there's only one name, we set it to one no matter what it was set to before.
+            if (scalar @names == 1)
+                {  $conversion{$names[0]} = 1;  }
+            else
+                {
+                # We sort the list first because we want the end result to be predictable.  This conversion could be happening on many
+                # machines, and they may not all specify the input directories in the same order.  They need to all come up with the
+                # same result.
+                @names = sort @names;
+
+                foreach my $name (@names)
+                    {
+                    if ($name eq 'default')
+                        {  $conversion{$name} = 1;  }
+                    else
+                        {
+                        $conversion{$name} = $currentNumber;
+                        $currentNumber++;
+                        };
+                    };
+                };
+
+            # Convert them to the new names.
+            foreach my $directory (keys %$inputHints)
+                {
+                $inputHints->{$directory} = $conversion{ $inputHints->{$directory} };
+                };
+            };
+
+
+        # Now we apply all the names from the hints, and save any unused ones as removed directories.
+
+        for (my $i = 0; $i < scalar @inputDirectories; $i++)
+            {
+            if (exists $inputHints->{$inputDirectories[$i]})
+                {
+                $inputDirectoryNames[$i] = $inputHints->{$inputDirectories[$i]};
+                $usedInputNames{ $inputDirectoryNames[$i] } = 1;
+                delete $inputHints->{$inputDirectories[$i]};
+                };
+            };
+
+
+        # Any remaining hints are saved as removed directories.
+
+        while (my ($directory, $name) = each %$inputHints)
+            {
+            push @removedInputDirectories, $directory;
+            push @removedInputDirectoryNames, $name;
+            };
+        };
+
+
+    if (defined $imageHints)
+        {
+        # Image directory names were never non-numeric, so there is no conversion.  Apply all the names from the hints.
+
+        for (my $i = 0; $i < scalar @imageDirectories; $i++)
+            {
+            if (exists $imageHints->{$imageDirectories[$i]})
+                {
+                $imageDirectoryNames[$i] = $imageHints->{$imageDirectories[$i]};
+                $usedImageNames{ $imageDirectoryNames[$i] } = 1;
+                delete $imageHints->{$imageDirectories[$i]};
+                };
+            };
+
+
+        # Any remaining hints are saved as removed directories.
+
+        while (my ($directory, $name) = each %$imageHints)
+            {
+            push @removedImageDirectories, $directory;
+            push @removedImageDirectoryNames, $name;
+            };
+        };
+
+
+    # Now we generate names for anything remaining.
+
+    my $inputCounter = 1;
+
+    for (my $i = 0; $i < scalar @inputDirectories; $i++)
+        {
+        if (!defined $inputDirectoryNames[$i])
+            {
+            while (exists $usedInputNames{$inputCounter})
+                {  $inputCounter++;  };
+
+            $inputDirectoryNames[$i] = $inputCounter;
+            $usedInputNames{$inputCounter} = 1;
+
+            $inputCounter++;
+            };
+        };
+
+
+    my $imageCounter = 1;
+
+    for (my $i = 0; $i < scalar @imageDirectories; $i++)
+        {
+        if (!defined $imageDirectoryNames[$i])
+            {
+            while (exists $usedImageNames{$imageCounter})
+                {  $imageCounter++;  };
+
+            $imageDirectoryNames[$i] = $imageCounter;
+            $usedImageNames{$imageCounter} = 1;
+
+            $imageCounter++;
+            };
+        };
+    };
+
+
+
+###############################################################################
+# Group: Information Functions
+
+
+#
+#   Function: InputDirectories
+#
+#   Returns an arrayref of input directories.  Do not change.
+#
+#   This will not return any removed input directories.
+#
+sub InputDirectories
+    {  return \@inputDirectories;  };
+
+#
+#   Function: InputDirectoryNameOf
+#
+#   Returns the generated name of the passed input directory.  <GenerateDirectoryNames()> must be called once before this
+#   function is available.
+#
+#   If a name for a removed input directory is available, it will be returned as well.
+#
+sub InputDirectoryNameOf #(directory)
+    {
+    my ($self, $directory) = @_;
+
+    for (my $i = 0; $i < scalar @inputDirectories; $i++)
+        {
+        if ($directory eq $inputDirectories[$i])
+            {  return $inputDirectoryNames[$i];  };
+        };
+
+    for (my $i = 0; $i < scalar @removedInputDirectories; $i++)
+        {
+        if ($directory eq $removedInputDirectories[$i])
+            {  return $removedInputDirectoryNames[$i];  };
+        };
+
+    return undef;
+    };
+
+
+#
+#   Function: SplitFromInputDirectory
+#
+#   Takes an input file name and returns the array ( inputDirectory, relativePath ).
+#
+#   If the file cannot be split from an input directory, it will try to do it with the removed input directories.
+#
+sub SplitFromInputDirectory #(file)
+    {
+    my ($self, $file) = @_;
+
+    foreach my $directory (@inputDirectories, @removedInputDirectories)
+        {
+        if (NaturalDocs::File->IsSubPathOf($directory, $file))
+            {  return ( $directory, NaturalDocs::File->MakeRelativePath($directory, $file) );  };
+        };
+
+    return ( );
+    };
+
+
+#
+#   Function: ImageDirectories
+#
+#   Returns an arrayref of image directories.  Do not change.
+#
+#   This will not return any removed image directories.
+#
+sub ImageDirectories
+    {  return \@imageDirectories;  };
+
+
+#
+#   Function: ImageDirectoryNameOf
+#
+#   Returns the generated name of the passed image or input directory.  <GenerateDirectoryNames()> must be called once before
+#   this function is available.
+#
+#   If a name for a removed input or image directory is available, it will be returned as well.
+#
+sub ImageDirectoryNameOf #(directory)
+    {
+    my ($self, $directory) = @_;
+
+    for (my $i = 0; $i < scalar @imageDirectories; $i++)
+        {
+        if ($directory eq $imageDirectories[$i])
+            {  return $imageDirectoryNames[$i];  };
+        };
+
+    for (my $i = 0; $i < scalar @removedImageDirectories; $i++)
+        {
+        if ($directory eq $removedImageDirectories[$i])
+            {  return $removedImageDirectoryNames[$i];  };
+        };
+
+    return undef;
+    };
+
+
+#
+#   Function: SplitFromImageDirectory
+#
+#   Takes an input image file name and returns the array ( imageDirectory, relativePath ).
+#
+#   If the file cannot be split from an image directory, it will try to do it with the removed image directories.
+#
+sub SplitFromImageDirectory #(file)
+    {
+    my ($self, $file) = @_;
+
+    foreach my $directory (@imageDirectories, @removedImageDirectories)
+        {
+        if (NaturalDocs::File->IsSubPathOf($directory, $file))
+            {  return ( $directory, NaturalDocs::File->MakeRelativePath($directory, $file) );  };
+        };
+
+    return ( );
+    };
+
+
+#
+#   Function: RelativeImageDirectories
+#
+#   Returns an arrayref of relative image directories.  Do not change.
+#
+sub RelativeImageDirectories
+    {  return \@relativeImageDirectories;  };
+
+
+# Function: ExcludedInputDirectories
+# Returns an arrayref of input directories to exclude.  Do not change.
+sub ExcludedInputDirectories
+    {  return \@excludedInputDirectories;  };
+
+
+# Function: BuildTargets
+# Returns an arrayref of <NaturalDocs::Settings::BuildTarget>s.  Do not change.
+sub BuildTargets
+    {  return \@buildTargets;  };
+
+
+#
+#   Function: OutputDirectoryOf
+#
+#   Returns the output directory of a builder object.
+#
+#   Parameters:
+#
+#       object - The builder object, whose class is derived from <NaturalDocs::Builder::Base>.
+#
+#   Returns:
+#
+#       The builder directory, or undef if the object wasn't found..
+#
+sub OutputDirectoryOf #(object)
+    {
+    my ($self, $object) = @_;
+
+    foreach my $buildTarget (@buildTargets)
+        {
+        if ($buildTarget->Builder() == $object)
+            {  return $buildTarget->Directory();  };
+        };
+
+    return undef;
+    };
+
+
+# Function: Styles
+# Returns an arrayref of the styles associated with the output.
+sub Styles
+    {  return \@styles;  };
+
+# Function: ProjectDirectory
+# Returns the project directory.
+sub ProjectDirectory
+    {  return $projectDirectory;  };
+
+# Function: ProjectDataDirectory
+# Returns the project data directory.
+sub ProjectDataDirectory
+    {  return NaturalDocs::File->JoinPaths($projectDirectory, 'Data', 1);  };
+
+# Function: StyleDirectory
+# Returns the main style directory.
+sub StyleDirectory
+    {  return NaturalDocs::File->JoinPaths($FindBin::RealBin, 'Styles', 1);  };
+
+# Function: JavaScriptDirectory
+# Returns the main JavaScript directory.
+sub JavaScriptDirectory
+    {  return NaturalDocs::File->JoinPaths($FindBin::RealBin, 'JavaScript', 1);  };
+
+# Function: ConfigDirectory
+# Returns the main configuration directory.
+sub ConfigDirectory
+    {  return NaturalDocs::File->JoinPaths($FindBin::RealBin, 'Config', 1);  };
+
+# Function: DocumentedOnly
+# Returns whether undocumented code aspects should be included in the output.
+sub DocumentedOnly
+    {  return $documentedOnly;  };
+
+# Function: TabLength
+# Returns the number of spaces tabs should be expanded to.
+sub TabLength
+    {  return $tabLength;  };
+
+# Function: NoAutoGroup
+# Returns whether auto-grouping is turned off.
+sub NoAutoGroup
+    {  return $noAutoGroup;  };
+
+# Function: OnlyFileTitles
+# Returns whether source files should always use the file name as the title.
+sub OnlyFileTitles
+    {  return $onlyFileTitles;  };
+
+# Function: IsQuiet
+# Returns whether the script should be run in quiet mode or not.
+sub IsQuiet
+    {  return $isQuiet;  };
+
+# Function: RebuildData
+# Returns whether all data files should be ignored and rebuilt.
+sub RebuildData
+    {  return $rebuildData;  };
+
+# Function: CharSet
+# Returns the character set, or undef if none.
+sub CharSet
+    {  return $charset;  };
+
+
+###############################################################################
+# Group: Constant Functions
+
+#
+#   Function: AppVersion
+#
+#   Returns Natural Docs' version number as an integer.  Use <TextAppVersion()> to get a printable version.
+#
+sub AppVersion
+    {
+    my ($self) = @_;
+    return NaturalDocs::Version->FromString($self->TextAppVersion());
+    };
+
+#
+#   Function: TextAppVersion
+#
+#   Returns Natural Docs' version number as plain text.
+#
+sub TextAppVersion
+    {
+    return '1.4';
+    };
+
+#
+#   Function: AppURL
+#
+#   Returns a string of the project's current web address.
+#
+sub AppURL
+    {  return 'http://www.naturaldocs.org';  };
+
+
+
+###############################################################################
+# Group: Support Functions
+
+
+#
+#   Function: ParseCommandLine
+#
+#   Parses and validates the command line.  Will cause the script to exit if the options ask for the syntax reference or
+#   are invalid.
+#
+sub ParseCommandLine
+    {
+    my ($self) = @_;
+
+    my %synonyms = ( 'input'    => '-i',
+                                  'source' => '-i',
+                                  'excludeinput' => '-xi',
+                                  'excludesource' => '-xi',
+                                  'images' => '-img',
+                                  'output'  => '-o',
+                                  'project' => '-p',
+                                  'documentedonly' => '-do',
+                                  'style'    => '-s',
+                                  'rebuild' => '-r',
+                                  'rebuildoutput' => '-ro',
+                                  'tablength' => '-t',
+                                  'quiet'    => '-q',
+                                  'headersonly' => '-ho',
+                                  'help'     => '-h',
+                                  'autogroup' => '-ag',
+                                  'noautogroup' => '-nag',
+                                  'onlyfiletitles' => '-oft',
+                                  'onlyfiletitle' => '-oft',
+                                  'charset' => '-cs',
+                                  'characterset' => '-cs' );
+
+
+    my @errorMessages;
+
+    my $valueRef;
+    my $option;
+
+    my @outputStrings;
+    my @imageStrings;
+
+
+    # Sometimes $valueRef is set to $ignored instead of undef because we don't want certain errors to cause other,
+    # unnecessary errors.  For example, if they set the input directory twice, we want to show that error and swallow the
+    # specified directory without complaint.  Otherwise it would complain about the directory too as if it were random crap
+    # inserted into the command line.
+    my $ignored;
+
+    my $index = 0;
+
+    while ($index < scalar @ARGV)
+        {
+        my $arg = $ARGV[$index];
+
+        if (substr($arg, 0, 1) eq '-')
+            {
+            $option = lc($arg);
+
+            # Support options like -t2 as well as -t 2.
+            if ($option =~ /^([^0-9]+)([0-9]+)$/)
+                {
+                $option = $1;
+                splice(@ARGV, $index + 1, 0, $2);
+                };
+
+            # Convert long forms to short.
+            if (substr($option, 1, 1) eq '-')
+                {
+                # Strip all dashes.
+                my $newOption = $option;
+                $newOption =~ tr/-//d;
+
+                if (exists $synonyms{$newOption})
+                    {  $option = $synonyms{$newOption};  }
+                }
+
+            if ($option eq '-i')
+                {
+                push @inputDirectories, undef;
+                $valueRef = \$inputDirectories[-1];
+                }
+            elsif ($option eq '-xi')
+                {
+                push @excludedInputDirectories, undef;
+                $valueRef = \$excludedInputDirectories[-1];
+                }
+            elsif ($option eq '-img')
+                {
+                push @imageStrings, undef;
+                $valueRef = \$imageStrings[-1];
+                }
+            elsif ($option eq '-p')
+                {
+                if (defined $projectDirectory)
+                    {
+                    push @errorMessages, 'You cannot have more than one project directory.';
+                    $valueRef = \$ignored;
+                    }
+                else
+                    {  $valueRef = \$projectDirectory;  };
+                }
+            elsif ($option eq '-o')
+                {
+                push @outputStrings, undef;
+                $valueRef = \$outputStrings[-1];
+                }
+            elsif ($option eq '-s')
+                {
+                $valueRef = \$styles[0];
+                }
+            elsif ($option eq '-t')
+                {
+                $valueRef = \$tabLength;
+                }
+            elsif ($option eq '-cs')
+                {
+                $valueRef = \$charset;
+                }
+            elsif ($option eq '-ag')
+                {
+                push @errorMessages, 'The -ag setting is no longer supported.  You can use -nag (--no-auto-group) to turn off '
+                                               . "auto-grouping, but there aren't multiple levels anymore.";
+                $valueRef = \$ignored;
+                }
+
+            # Options that aren't followed by content.
+            else
+                {
+                $valueRef = undef;
+
+                if ($option eq '-r')
+                    {
+                    NaturalDocs::Project->ReparseEverything();
+                    NaturalDocs::Project->RebuildEverything();
+                    $rebuildData = 1;
+                    }
+                elsif ($option eq '-ro')
+                    {
+                    NaturalDocs::Project->RebuildEverything();
+                    }
+                elsif ($option eq '-do')
+                    {  $documentedOnly = 1;  }
+                elsif ($option eq '-oft')
+                    {  $onlyFileTitles = 1;  }
+                elsif ($option eq '-q')
+                    {  $isQuiet = 1;  }
+                elsif ($option eq '-ho')
+                    {
+                    push @errorMessages, 'The -ho setting is no longer supported.  You can have Natural Docs skip over the source file '
+                                                   . 'extensions by editing Languages.txt in your project directory.';
+                    }
+                elsif ($option eq '-nag')
+                    {  $noAutoGroup = 1;  }
+                elsif ($option eq '-?' || $option eq '-h')
+                    {
+                    $self->PrintSyntax();
+                    exit;
+                    }
+                else
+                    {  push @errorMessages, 'Unrecognized option ' . $option;  };
+
+                };
+
+            }
+
+        # Is a segment of text, not an option...
+        else
+            {
+            if (defined $valueRef)
+                {
+                # We want to preserve spaces in paths.
+                if (defined $$valueRef)
+                    {  $$valueRef .= ' ';  };
+
+                $$valueRef .= $arg;
+                }
+
+            else
+                {
+                push @errorMessages, 'Unrecognized element ' . $arg;
+                };
+            };
+
+        $index++;
+        };
+
+
+    # Validate the style, if specified.
+
+    if ($styles[0])
+        {
+        my @stylePieces = split(/ +/, $styles[0]);
+        @styles = ( );
+
+        while (scalar @stylePieces)
+            {
+            if (lc($stylePieces[0]) eq 'custom')
+                {
+                push @errorMessages, 'The "Custom" style setting is no longer supported.  Copy your custom style sheet to your '
+                                               . 'project directory and you can refer to it with -s.';
+                shift @stylePieces;
+                }
+            else
+                {
+                # People may use styles with spaces in them.  If a style doesn't exist, we need to join the pieces until we find one that
+                # does or we run out of pieces.
+
+                my $extras = 0;
+                my $success;
+
+                while ($extras < scalar @stylePieces)
+                    {
+                    my $style;
+
+                    if (!$extras)
+                        {  $style = $stylePieces[0];  }
+                    else
+                        {  $style = join(' ', @stylePieces[0..$extras]);  };
+
+                    my $cssFile = NaturalDocs::File->JoinPaths( $self->StyleDirectory(), $style . '.css' );
+                    if (-e $cssFile)
+                        {
+                        push @styles, $style;
+                        splice(@stylePieces, 0, 1 + $extras);
+                        $success = 1;
+                        last;
+                        }
+                    else
+                        {
+                        $cssFile = NaturalDocs::File->JoinPaths( $self->ProjectDirectory(), $style . '.css' );
+
+                        if (-e $cssFile)
+                            {
+                            push @styles, $style;
+                            splice(@stylePieces, 0, 1 + $extras);
+                            $success = 1;
+                            last;
+                            }
+                        else
+                            {  $extras++;  };
+                        };
+                    };
+
+                if (!$success)
+                    {
+                    push @errorMessages, 'The style "' . $stylePieces[0] . '" does not exist.';
+                    shift @stylePieces;
+                    };
+                };
+            };
+        }
+    else
+        {  @styles = ( 'Default' );  };
+
+
+    # Decode and validate the output strings.
+
+    my %outputDirectories;
+
+    foreach my $outputString (@outputStrings)
+        {
+        my ($format, $directory) = split(/ /, $outputString, 2);
+
+        if (!defined $directory)
+            {  push @errorMessages, 'The -o option needs two parameters: -o [format] [directory]';  }
+        else
+            {
+            if (!NaturalDocs::File->PathIsAbsolute($directory))
+                {  $directory = NaturalDocs::File->JoinPaths(Cwd::cwd(), $directory, 1);  };
+
+            $directory = NaturalDocs::File->CanonizePath($directory);
+
+            if (! -e $directory || ! -d $directory)
+                {
+                # They may have forgotten the format portion and the directory name had a space in it.
+                if (-e ($format . ' ' . $directory) && -d ($format . ' ' . $directory))
+                    {
+                    push @errorMessages, 'The -o option needs two parameters: -o [format] [directory]';
+                    $format = undef;
+                    }
+                else
+                    {  push @errorMessages, 'The output directory ' . $directory . ' does not exist.';  }
+                }
+            elsif (exists $outputDirectories{$directory})
+                {  push @errorMessages, 'You cannot specify the output directory ' . $directory . ' more than once.';  }
+            else
+                {  $outputDirectories{$directory} = 1;  };
+
+            if (defined $format)
+                {
+                my $builderPackage = NaturalDocs::Builder->OutputPackageOf($format);
+
+                if (defined $builderPackage)
+                    {
+                    push @buildTargets,
+                            NaturalDocs::Settings::BuildTarget->New($builderPackage->New(), $directory);
+                    }
+                else
+                    {
+                    push @errorMessages, 'The output format ' . $format . ' doesn\'t exist or is not installed.';
+                    $valueRef = \$ignored;
+                    };
+                };
+            };
+        };
+
+    if (!scalar @buildTargets)
+        {  push @errorMessages, 'You did not specify an output directory.';  };
+
+
+    # Decode and validate the image strings.
+
+    foreach my $imageString (@imageStrings)
+        {
+        if ($imageString =~ /^ *\*/)
+            {
+            # The below NaturalDocs::File functions assume everything is canonized.
+            $imageString = NaturalDocs::File->CanonizePath($imageString);
+
+            my ($volume, $directoryString) = NaturalDocs::File->SplitPath($imageString, 1);
+            my @directories = NaturalDocs::File->SplitDirectories($directoryString);
+
+            shift @directories;
+
+            $directoryString = NaturalDocs::File->JoinDirectories(@directories);
+            push @relativeImageDirectories, NaturalDocs::File->JoinPath($volume, $directoryString);
+            }
+        else
+            {
+            if (!NaturalDocs::File->PathIsAbsolute($imageString))
+                {  $imageString = NaturalDocs::File->JoinPaths(Cwd::cwd(), $imageString, 1);  };
+
+            $imageString = NaturalDocs::File->CanonizePath($imageString);
+
+            if (! -e $imageString || ! -d $imageString)
+                {  push @errorMessages, 'The image directory ' . $imageString . ' does not exist.';  };
+
+            push @imageDirectories, $imageString;
+            };
+        };
+
+
+    # Make sure the input and project directories are specified, canonized, and exist.
+
+    if (scalar @inputDirectories)
+        {
+        for (my $i = 0; $i < scalar @inputDirectories; $i++)
+            {
+            if (!NaturalDocs::File->PathIsAbsolute($inputDirectories[$i]))
+                {  $inputDirectories[$i] = NaturalDocs::File->JoinPaths(Cwd::cwd(), $inputDirectories[$i], 1);  };
+
+            $inputDirectories[$i] = NaturalDocs::File->CanonizePath($inputDirectories[$i]);
+
+            if (! -e $inputDirectories[$i] || ! -d $inputDirectories[$i])
+                {  push @errorMessages, 'The input directory ' . $inputDirectories[$i] . ' does not exist.';  };
+            };
+        }
+    else
+        {  push @errorMessages, 'You did not specify an input (source) directory.';  };
+
+    if (defined $projectDirectory)
+        {
+        if (!NaturalDocs::File->PathIsAbsolute($projectDirectory))
+            {  $projectDirectory = NaturalDocs::File->JoinPaths(Cwd::cwd(), $projectDirectory, 1);  };
+
+        $projectDirectory = NaturalDocs::File->CanonizePath($projectDirectory);
+
+        if (! -e $projectDirectory || ! -d $projectDirectory)
+            {  push @errorMessages, 'The project directory ' . $projectDirectory . ' does not exist.';  };
+
+        # Create the Data subdirectory if it doesn't exist.
+        NaturalDocs::File->CreatePath( NaturalDocs::File->JoinPaths($projectDirectory, 'Data', 1) );
+        }
+    else
+        {  push @errorMessages, 'You did not specify a project directory.';  };
+
+
+    # Make sure the excluded input directories are canonized, and add the project and output directories to the list.
+
+    for (my $i = 0; $i < scalar @excludedInputDirectories; $i++)
+        {
+        if (!NaturalDocs::File->PathIsAbsolute($excludedInputDirectories[$i]))
+            {  $excludedInputDirectories[$i] = NaturalDocs::File->JoinPaths(Cwd::cwd(), $excludedInputDirectories[$i], 1);  };
+
+        $excludedInputDirectories[$i] = NaturalDocs::File->CanonizePath($excludedInputDirectories[$i]);
+        };
+
+    push @excludedInputDirectories, $projectDirectory;
+
+    foreach my $buildTarget (@buildTargets)
+        {
+        push @excludedInputDirectories, $buildTarget->Directory();
+        };
+
+
+    # Determine the tab length, and default to four if not specified.
+
+    if (defined $tabLength)
+        {
+        if ($tabLength !~ /^[0-9]+$/)
+            {  push @errorMessages, 'The tab length must be a number.';  };
+        }
+    else
+        {  $tabLength = 4;  };
+
+
+    # Strip any quotes off of the charset.
+    $charset =~ tr/\"//d;
+
+
+    # Exit with the error message if there was one.
+
+    if (scalar @errorMessages)
+        {
+        print join("\n", @errorMessages) . "\nType NaturalDocs -h to see the syntax reference.\n";
+        exit;
+        };
+    };
+
+#
+#   Function: PrintSyntax
+#
+#   Prints the syntax reference.
+#
+sub PrintSyntax
+    {
+    my ($self) = @_;
+
+    # Make sure all line lengths are under 80 characters.
+
+    print
+
+    "Natural Docs, version " . $self->TextAppVersion() . "\n"
+    . $self->AppURL() . "\n"
+    . "This program is licensed under the GPL\n"
+    . "--------------------------------------\n"
+    . "\n"
+    . "Syntax:\n"
+    . "\n"
+    . "    NaturalDocs -i [input (source) directory]\n"
+    . "               (-i [input (source) directory] ...)\n"
+    . "                -o [output format] [output directory]\n"
+    . "               (-o [output format] [output directory] ...)\n"
+    . "                -p [project directory]\n"
+    . "                [options]\n"
+    . "\n"
+    . "Examples:\n"
+    . "\n"
+    . "    NaturalDocs -i C:\\My Project\\Source -o HTML C:\\My Project\\Docs\n"
+    . "                -p C:\\My Project\\Natural Docs\n"
+    . "    NaturalDocs -i /src/project -o HTML /doc/project\n"
+    . "                -p /etc/naturaldocs/project -s Small -q\n"
+    . "\n"
+    . "Required Parameters:\n"
+    . "\n"
+    . " -i [dir]\n--input [dir]\n--source [dir]\n"
+    . "     Specifies an input (source) directory.  Required.\n"
+    . "     Can be specified multiple times.\n"
+    . "\n"
+    . " -o [fmt] [dir]\n--output [fmt] [dir]\n"
+    . "    Specifies an output format and directory.  Required.\n"
+    . "    Can be specified multiple times, but only once per directory.\n"
+    . "    Possible output formats:\n";
+
+    $self->PrintOutputFormats('    - ');
+
+    print
+    "\n"
+    . " -p [dir]\n--project [dir]\n"
+    . "    Specifies the project directory.  Required.\n"
+    . "    There needs to be a unique project directory for every source directory.\n"
+    . "\n"
+    . "Optional Parameters:\n"
+    . "\n"
+    . " -s [style] ([style] [style] ...)\n--style [style] ([style] [style] ...)\n"
+    . "    Specifies the CSS style when building HTML output.  If multiple styles are\n"
+    . "    specified, they will all be included in the order given.\n"
+    . "\n"
+    . " -img [image directory]\n--image [image directory]"
+    . "    Specifies an image directory.  Can be specified multiple times.\n"
+    . "    Start with * to specify a relative directory, as in -img */images.\n"
+    . "\n"
+    . " -do\n--documented-only\n"
+    . "    Specifies only documented code aspects should be included in the output.\n"
+    . "\n"
+    . " -t [len]\n--tab-length [len]\n"
+    . "    Specifies the number of spaces tabs should be expanded to.  This only needs\n"
+    . "    to be set if you use tabs in example code and text diagrams.  Defaults to 4.\n"
+    . "\n"
+    . " -xi [dir]\n--exclude-input [dir]\n--exclude-source [dir]\n"
+    . "    Excludes an input (source) directory from the documentation.\n"
+    . "    Automatically done for the project and output directories.  Can\n"
+    . "    be specified multiple times.\n"
+    . "\n"
+    . " -nag\n--no-auto-group\n"
+    . "    Turns off auto-grouping completely.\n"
+    . "\n"
+    . " -oft\n--only-file-titles\n"
+    . "    Source files will only use the file name as the title.\n"
+    . "\n"
+    . " -r\n--rebuild\n"
+    . "    Rebuilds all output and data files from scratch.\n"
+    . "    Does not affect the menu file.\n"
+    . "\n"
+    . " -ro\n--rebuild-output\n"
+    . "    Rebuilds all output files from scratch.\n"
+    . "\n"
+    . " -q\n--quiet\n"
+    . "    Suppresses all non-error output.\n"
+    . "\n"
+    . " -?\n -h\n--help\n"
+    . "    Displays this syntax reference.\n";
+    };
+
+
+#
+#   Function: PrintOutputFormats
+#
+#   Prints all the possible output formats that can be specified with -o.  Each one will be placed on its own line.
+#
+#   Parameters:
+#
+#       prefix - Characters to prefix each one with, such as for indentation.
+#
+sub PrintOutputFormats #(prefix)
+    {
+    my ($self, $prefix) = @_;
+
+    my $outputPackages = NaturalDocs::Builder::OutputPackages();
+
+    foreach my $outputPackage (@$outputPackages)
+        {
+        print $prefix . $outputPackage->CommandLineOption() . "\n";
+        };
+    };
+
+
+#
+#   Function: LoadAndComparePreviousSettings
+#
+#   Loads <PreviousSettings.nd> and compares the values there with those in the command line.  If differences require it,
+#   sets <rebuildData> and/or <rebuildOutput>.
+#
+sub LoadAndComparePreviousSettings
+    {
+    my ($self) = @_;
+
+    my $fileIsOkay;
+
+    if (!NaturalDocs::Settings->RebuildData())
+        {
+        my $version;
+
+        if (NaturalDocs::BinaryFile->OpenForReading( NaturalDocs::Project->DataFile('PreviousSettings.nd'),
+                                                                           NaturalDocs::Version->FromString('1.4') ))
+            {  $fileIsOkay = 1;  };
+        };
+
+    if (!$fileIsOkay)
+        {
+        # We need to reparse everything because --documented-only may have changed.
+        # We need to rebuild everything because --tab-length may have changed.
+        NaturalDocs::Project->ReparseEverything();
+        NaturalDocs::Project->RebuildEverything();
+        }
+    else
+        {
+        my $raw;
+  
+        # [UInt8: tab expansion]
+        # [UInt8: documented only (0 or 1)]
+        # [UInt8: no auto-group (0 or 1)]
+        # [UInt8: only file titles (0 or 1)]
+        # [AString16: charset]
+
+        my $prevTabLength = NaturalDocs::BinaryFile->GetUInt8();
+        my $prevDocumentedOnly = NaturalDocs::BinaryFile->GetUInt8();
+        my $prevNoAutoGroup = NaturalDocs::BinaryFile->GetUInt8();
+        my $prevOnlyFileTitles = NaturalDocs::BinaryFile->GetUInt8();
+        my $prevCharset = NaturalDocs::BinaryFile->GetAString16();
+
+        if ($prevTabLength != $self->TabLength())
+            {
+            # We need to rebuild all output because this affects all text diagrams.
+            NaturalDocs::Project->RebuildEverything();
+            };
+
+        if ($prevDocumentedOnly == 0)
+            {  $prevDocumentedOnly = undef;  };
+        if ($prevNoAutoGroup == 0)
+            {  $prevNoAutoGroup = undef;  };
+        if ($prevOnlyFileTitles == 0)
+            {  $prevOnlyFileTitles = undef;  };
+
+        if ($prevDocumentedOnly != $self->DocumentedOnly() ||
+            $prevNoAutoGroup != $self->NoAutoGroup() ||
+            $prevOnlyFileTitles != $self->OnlyFileTitles())
+            {
+            NaturalDocs::Project->ReparseEverything();
+            };
+
+        if ($prevCharset ne $charset)
+            {  NaturalDocs::Project->RebuildEverything();  };
+
+
+        # [UInt8: number of input directories]
+
+        my $inputDirectoryCount = NaturalDocs::BinaryFile->GetUInt8();
+
+        while ($inputDirectoryCount)
+            {
+            # [AString16: input directory] [AString16: input directory name] ...
+
+            my $inputDirectory = NaturalDocs::BinaryFile->GetAString16();
+            my $inputDirectoryName = NaturalDocs::BinaryFile->GetAString16();
+
+            # Not doing anything with this for now.
+
+            $inputDirectoryCount--;
+            };
+
+
+        # [UInt8: number of output targets]
+
+        my $outputTargetCount = NaturalDocs::BinaryFile->GetUInt8();
+
+        # Keys are the directories, values are the command line options.
+        my %previousOutputDirectories;
+
+        while ($outputTargetCount)
+            {
+            # [AString16: output directory] [AString16: output format command line option] ...
+
+            my $outputDirectory = NaturalDocs::BinaryFile->GetAString16();
+            my $outputCommand = NaturalDocs::BinaryFile->GetAString16();
+
+            $previousOutputDirectories{$outputDirectory} = $outputCommand;
+
+            $outputTargetCount--;
+            };
+
+        # Check if any targets were added to the command line, or if their formats changed.  We don't care if targets were
+        # removed.
+        my $buildTargets = $self->BuildTargets();
+
+        foreach my $buildTarget (@$buildTargets)
+            {
+            if (!exists $previousOutputDirectories{$buildTarget->Directory()} ||
+                $buildTarget->Builder()->CommandLineOption() ne $previousOutputDirectories{$buildTarget->Directory()})
+                {
+                NaturalDocs::Project->RebuildEverything();
+                last;
+                };
+            };
+
+        NaturalDocs::BinaryFile->Close();
+        };
+    };
+
+
+#
+#   Function: SavePreviousSettings
+#
+#   Saves the settings into <PreviousSettings.nd>.
+#
+sub SavePreviousSettings
+    {
+    my ($self) = @_;
+
+    NaturalDocs::BinaryFile->OpenForWriting(  NaturalDocs::Project->DataFile('PreviousSettings.nd') );
+
+    # [UInt8: tab length]
+    # [UInt8: documented only (0 or 1)]
+    # [UInt8: no auto-group (0 or 1)]
+    # [UInt8: only file titles (0 or 1)]
+    # [AString16: charset]
+    # [UInt8: number of input directories]
+
+    my $inputDirectories = $self->InputDirectories();
+
+    NaturalDocs::BinaryFile->WriteUInt8($self->TabLength());
+    NaturalDocs::BinaryFile->WriteUInt8($self->DocumentedOnly() ? 1 : 0);
+    NaturalDocs::BinaryFile->WriteUInt8($self->NoAutoGroup() ? 1 : 0);
+    NaturalDocs::BinaryFile->WriteUInt8($self->OnlyFileTitles() ? 1 : 0);
+    NaturalDocs::BinaryFile->WriteAString16($charset);
+    NaturalDocs::BinaryFile->WriteUInt8(scalar @$inputDirectories);
+
+    foreach my $inputDirectory (@$inputDirectories)
+        {
+        my $inputDirectoryName = $self->InputDirectoryNameOf($inputDirectory);
+
+        # [AString16: input directory] [AString16: input directory name] ...
+        NaturalDocs::BinaryFile->WriteAString16($inputDirectory);
+        NaturalDocs::BinaryFile->WriteAString16($inputDirectoryName);
+        };
+
+    # [UInt8: number of output targets]
+
+    my $buildTargets = $self->BuildTargets();
+    NaturalDocs::BinaryFile->WriteUInt8(scalar @$buildTargets);
+
+    foreach my $buildTarget (@$buildTargets)
+        {
+        # [AString16: output directory] [AString16: output format command line option] ...
+        NaturalDocs::BinaryFile->WriteAString16( $buildTarget->Directory() );
+        NaturalDocs::BinaryFile->WriteAString16( $buildTarget->Builder()->CommandLineOption() );
+        };
+
+    NaturalDocs::BinaryFile->Close();
+    };
+
+
+1;

Added: trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/SourceDB/Extension.pm
===================================================================
--- trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/SourceDB/Extension.pm	                        (rev 0)
+++ trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/SourceDB/Extension.pm	2009-07-01 15:30:00 UTC (rev 4275)
@@ -0,0 +1,84 @@
+###############################################################################
+#
+#   Package: NaturalDocs::SourceDB::Extension
+#
+###############################################################################
+#
+#   A base package for all <SourceDB> extensions.
+#
+###############################################################################
+
+# 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::SourceDB::Extension;
+
+
+###############################################################################
+# Group: Interface Functions
+# These functions must be overridden by the derived class.
+
+
+#
+#   Function: Register
+#
+#   Override this function to register the package with <NaturalDocs::SourceDB->RegisterExtension()>.
+#
+sub Register
+    {
+    die "Called SourceDB::Extension->Register().  This function should be overridden by every extension.";
+    };
+
+
+#
+#   Function: Load
+#
+#   Called by <NaturalDocs::SourceDB->Load()> to load the extension's data.  Returns whether it was successful.
+#
+#   *This function might not be called.*  If there's a situation that would cause all the source files to be reparsed anyway,
+#   <NaturalDocs::SourceDB> may skip calling Load() for the remaining extensions.  You should *not* depend on this function
+#   for any critical initialization that needs to happen every time regardless.
+#
+sub Load # => bool
+    {
+    return 1;
+    };
+
+
+#
+#   Function: Save
+#
+#   Called by <NaturalDocs::SourceDB->Save()> to save the extension's data.
+#
+sub Save
+    {
+    };
+
+
+#
+#   Function: OnDeletedDefinition
+#
+#   Called for each definition deleted by <NaturalDocs::SourceDB>.  This is called *after* the definition has been deleted from
+#   the database, so don't expect to be able to read it.
+#
+sub OnDeletedDefinition #(string itemString, FileName file, bool wasLastDefinition)
+    {
+    };
+
+
+#
+#   Function: OnChangedDefinition
+#
+#   Called for each definition changed by <NaturalDocs::SourceDB>.  This is called *after* the definition has been changed, so
+#   don't expect to be able to read the original value.
+#
+sub OnChangedDefinition #(string itemString, FileName file)
+    {
+    };
+
+
+1;

Added: trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/SourceDB/File.pm
===================================================================
--- trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/SourceDB/File.pm	                        (rev 0)
+++ trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/SourceDB/File.pm	2009-07-01 15:30:00 UTC (rev 4275)
@@ -0,0 +1,129 @@
+###############################################################################
+#
+#   Package: NaturalDocs::SourceDB::File
+#
+###############################################################################
+#
+#   A class used to index items by file.
+#
+###############################################################################
+
+# 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::SourceDB::File;
+
+use NaturalDocs::DefineMembers 'ITEMS';
+
+
+#
+#   Variables: Members
+#
+#   These constants serve as indexes into the object array.
+#
+#   ITEMS - An arrayref where an <ExtensionID> is the index and the members are existence hashrefs of the item strigs defined
+#               in this file.  The arrayref will always exist, but the hashrefs may be undef.
+#
+
+
+#
+#   Function: New
+#
+#   Returns a new object.
+#
+sub New
+    {
+    my $package = shift;
+
+    my $object = [ ];
+    $object->[ITEMS] = [ ];
+
+    bless $object, $package;
+    return $object;
+    };
+
+
+#
+#   Function: AddItem
+#
+#   Adds an item to this file.  Returns whether this added a new item.
+#
+sub AddItem #(ExtensionID extension, string itemString) => bool
+    {
+    my ($self, $extension, $itemString) = @_;
+
+    if (!defined $self->[ITEMS]->[$extension])
+        {
+        $self->[ITEMS]->[$extension] = { $itemString => 1 };
+        return 1;
+        }
+    elsif (!exists $self->[ITEMS]->[$extension]->{$itemString})
+        {
+        $self->[ITEMS]->[$extension]->{$itemString} = 1;
+        return 1;
+        }
+    else
+        {
+        return 0;
+        };
+    };
+
+
+#
+#   Function: HasItem
+#
+#   Returns whether the item exists in this file.
+#
+sub HasItem #(ExtensionID extension, string itemString) => bool
+    {
+    my ($self, $extension, $itemString) = @_;
+
+    if (defined $self->[ITEMS]->[$extension])
+        {  return exists $self->[ITEMS]->[$extension]->{$itemString};  }
+    else
+        {  return 0;  };
+    };
+
+
+#
+#   Function: DeleteItem
+#
+#   Deletes the passed item.  Returns whether it existed.
+#
+sub DeleteItem #(ExtensionID extension, string itemString) => bool
+    {
+    my ($self, $extension, $itemString) = @_;
+
+    if (!defined $self->[ITEMS]->[$extension])
+        {  return 0;  }
+    elsif (exists $self->[ITEMS]->[$extension]->{$itemString})
+        {
+        delete $self->[ITEMS]->[$extension]->{$itemString};
+        return 1;
+        }
+    else
+        {  return 0;  };
+    };
+
+
+#
+#   Function: ListItems
+#
+#   Returns an array of all the item strings defined for a particular extension, or an empty list if none.
+#
+sub ListItems #(ExtensionID extension) => string array
+    {
+    my ($self, $extension) = @_;
+
+    if (defined $self->[ITEMS]->[$extension])
+        {  return keys %{$self->[ITEMS]->[$extension]};  }
+    else
+        {  return ( );  };
+    };
+
+
+1;

Added: trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/SourceDB/Item.pm
===================================================================
--- trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/SourceDB/Item.pm	                        (rev 0)
+++ trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/SourceDB/Item.pm	2009-07-01 15:30:00 UTC (rev 4275)
@@ -0,0 +1,201 @@
+###############################################################################
+#
+#   Package: NaturalDocs::SourceDB::Item
+#
+###############################################################################
+#
+#   A base class for something being tracked in <NaturalDocs::SourceDB>.
+#
+###############################################################################
+
+# 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::SourceDB::Item;
+
+use NaturalDocs::DefineMembers 'DEFINITIONS';
+
+
+#
+#   Variables: Members
+#
+#   The following constants are indexes into the object array.
+#
+#   DEFINITIONS - A hashref that maps <FileNames> to either <NaturalDocs::SourceDB::ItemDefinition>-derived objects or
+#                         serves as an existence hashref depending on whether the extension only tracks existence.  Will be undef if
+#                         there are none.
+#
+
+
+#
+#   Function: New
+#
+#   Creates and returns a new object.
+#
+sub New
+    {
+    my $class = shift;
+
+    my $object = [ ];
+    bless $object, $class;
+
+    return $object;
+    };
+
+
+
+###############################################################################
+#
+#   Group: Definition Functions
+#
+#   These functions should be called by <NaturalDocs::SourceDB>.  You should not be calling them directly.  Call functions
+#   like <NaturalDocs::SourceDB->AddDefinition()> instead.
+#
+
+
+#
+#   Function: AddDefinition
+#
+#   Adds a definition for the passed <FileName>.  If it's already defined, the new definition will be ignored.
+#
+#   Parameters:
+#
+#       file - The <FileName>.
+#       definition - The definition, which must be an object derived from <NaturalDocs::SourceDB::ItemDefinition> or undef if
+#                       the extension only tracks existence.
+#
+#   Returns:
+#
+#       Whether the definition was added, which is to say, whether this was the first definition for the passed <FileName>.
+#
+sub AddDefinition #(FileName file, optional NaturalDocs::SourceDB::ItemDefinition definition) => bool
+    {
+    my ($self, $file, $definition) = @_;
+
+    if (!defined $self->[DEFINITIONS])
+        {  $self->[DEFINITIONS] = { };  };
+
+    if (!exists $self->[DEFINITIONS]->{$file})
+        {
+        if (!defined $definition)
+            {  $definition = 1;  };
+
+        $self->[DEFINITIONS]->{$file} = $definition;
+        return 1;
+        }
+    else
+        {  return 0;  };
+    };
+
+
+#
+#   Function: ChangeDefinition
+#
+#   Changes the definition for the passed <FileName>.
+#
+#   Parameters:
+#
+#       file - The <FileName>.
+#       definition - The definition, which must be an object derived from <NaturalDocs::SourceDB::ItemDefinition>.
+#
+sub ChangeDefinition #(FileName file, NaturalDocs::SourceDB::ItemDefinition definition)
+    {
+    my ($self, $file, $definition) = @_;
+
+    if (!defined $self->[DEFINITIONS] || !exists $self->[DEFINITIONS]->{$file})
+        {  die "Tried to change a non-existant definition in SourceD::Item.";  };
+
+    $self->[DEFINITIONS]->{$file} = $definition;
+    };
+
+
+#
+#   Function: GetDefinition
+#
+#   Returns the <NaturalDocs::SourceDB::ItemDefinition>-derived object for the passed <FileName>, non-zero if it only tracks
+#   existence, or undef if there is no definition.
+#
+sub GetDefinition #(FileName file) => NaturalDocs::SourceDB::ItemDefinition or bool
+    {
+    my ($self, $file) = @_;
+
+    if (defined $self->[DEFINITIONS])
+        {  return $self->[DEFINITIONS]->{$file};  }
+    else
+        {  return undef;  };
+    };
+
+
+#
+#   Function: DeleteDefinition
+#
+#   Removes the definition for the passed <FileName>.  Returns whether it was successful, meaning whether a definition existed
+#   for that file.
+#
+sub DeleteDefinition #(FileName file) => bool
+    {
+    my ($self, $file) = @_;
+
+    if (defined $self->[DEFINITIONS])
+        {
+        if (exists $self->[DEFINITIONS]->{$file})
+            {
+            delete $self->[DEFINITIONS]->{$file};
+
+            if (!scalar keys %{$self->[DEFINITIONS]})
+                {  $self->[DEFINITIONS] = undef;  };
+
+            return 1;
+            };
+        };
+
+    return 0;
+    };
+
+
+#
+#   Function: HasDefinitions
+#
+#   Returns whether there are any definitions for this item.
+#
+sub HasDefinitions # => bool
+    {
+    my $self = shift;
+    return (defined $self->[DEFINITIONS]);
+    };
+
+
+#
+#   Function: HasDefinition
+#
+#   Returns whether there is a definition for the passed <FileName>.
+#
+sub HasDefinition #(FileName file) => bool
+    {
+    my ($self, $file) = @_;
+
+    if (defined $self->[DEFINITIONS])
+        {  return (exists $self->[DEFINITIONS]->{$file});  }
+    else
+        {  return 0;  };
+    };
+
+
+#
+#   Function: GetAllDefinitionsHashRef
+#
+#   Returns a hashref of all the definitions of this item.  *Do not change.*  The keys are the <FileNames>, and the values are
+#   either <NaturalDocs::SourceDB::ItemDefinition>-derived objects or it's just an existence hashref if those aren't used.
+#
+sub GetAllDefinitionsHashRef
+    {
+    my $self = shift;
+    return $self->[DEFINITIONS];
+    };
+
+
+1;

Added: trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/SourceDB/ItemDefinition.pm
===================================================================
--- trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/SourceDB/ItemDefinition.pm	                        (rev 0)
+++ trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/SourceDB/ItemDefinition.pm	2009-07-01 15:30:00 UTC (rev 4275)
@@ -0,0 +1,45 @@
+###############################################################################
+#
+#   Package: NaturalDocs::SourceDB::ItemDefinition
+#
+###############################################################################
+#
+#   A base class for all item definitions for extensions that track more than existence.
+#
+###############################################################################
+
+# 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::SourceDB::ItemDefinition;
+
+
+#
+#   Function: Compare
+#
+#   Returns whether the definitions are equal.  This version returns true by default, you must override it in your subclasses
+#   to make the results relevant.  This is important for <NaturalDocs::SourceDB->AnalyzeTrackedFileChanges()>.
+#
+#   This will only be called between objects of the same <ExtensionID>.  If you use multiple derived classes for the same
+#   <ExtensionID>, you will have to take that into account yourself.
+#
+#   Parameters:
+#
+#       other - Another <NaturalDocs::SourceDB::ItemDefinition>-derived object to compare this one to.  It will always be from
+#                  the same <ExtensionID>.
+#
+#   Returns:
+#
+#       Whether they are equal.
+#
+sub Compare #(other)
+    {
+    return 1;
+    };
+
+
+1;

Added: trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/SourceDB/WatchedFileDefinitions.pm
===================================================================
--- trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/SourceDB/WatchedFileDefinitions.pm	                        (rev 0)
+++ trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/SourceDB/WatchedFileDefinitions.pm	2009-07-01 15:30:00 UTC (rev 4275)
@@ -0,0 +1,159 @@
+###############################################################################
+#
+#   Package: NaturalDocs::SourceDB::WatchedFileDefinitions
+#
+###############################################################################
+#
+#   A class to track the definitions appearing in a watched file.  This is only used for extensions that track definition info with
+#   <NaturalDocs::SourceDB::ItemDefinition>-derived objects.  Do not use it for extensions that only track existence.
+#
+###############################################################################
+
+# 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::SourceDB::WatchedFileDefinitions;
+
+
+#
+#   Variables: Members
+#
+#   This object would only have one member, which is an array, so the object itself serves as that member.
+#
+#   <ExtensionIDs> are used as indexes into this object.  Each entry is a hashref that maps item strings to
+#   <NaturalDocs::SourceDB::ItemDefinition>-derived objects.  This is only done for extensions that use those objects to track
+#   definitions, it's not needed for extensions that only track existence.  If there are no definitions, the entry will be undef.
+#
+
+
+#
+#   Function: New
+#
+#   Creates and returns a new object.
+#
+sub New
+    {
+    my $class = shift;
+
+    my $object = [ ];
+    bless $object, $class;
+
+    return $object;
+    };
+
+
+
+###############################################################################
+# Group: Definition Functions
+#
+
+
+#
+#   Function: AddDefinition
+#
+#   Adds a definition for the passed item string.  If it's already defined, the new definition will be ignored.
+#
+#   Parameters:
+#
+#       extension - The <ExtensionID>.
+#       itemString - The item string.
+#       definition - The definition, which must be an object derived from <NaturalDocs::SourceDB::ItemDefinition>.
+#
+#   Returns:
+#
+#       Whether the definition was added, which is to say, whether this was the first definition for the passed <FileName>.
+#
+sub AddDefinition #(ExtensionID extension, string itemString, NaturalDocs::SourceDB::ItemDefinition definition) => bool
+    {
+    my ($self, $extension, $itemString, $definition) = @_;
+
+    if (!defined $self->[$extension])
+        {  $self->[$extension] = { };  };
+
+    if (!exists $self->[$extension]->{$itemString})
+        {
+        $self->[$extension]->{$itemString} = $definition;
+        return 1;
+        }
+    else
+        {  return 0;  };
+    };
+
+
+#
+#   Function: GetDefinition
+#
+#   Returns the <NaturalDocs::SourceDB::ItemDefinition>-derived object for the passed item string  or undef if there is none.
+#
+sub GetDefinition #(ExtensionID extension, string itemString) => NaturalDocs::SourceDB::ItemDefinition
+    {
+    my ($self, $extension, $itemString) = @_;
+
+    if (defined $self->[$extension])
+        {  return $self->[$extension]->{$itemString};  }
+    else
+        {  return undef;  };
+    };
+
+
+#
+#   Function: DeleteDefinition
+#
+#   Removes the definition for the passed item string.  Returns whether it was successful, meaning whether a definition existed
+#   for that item.
+#
+sub DeleteDefinition #(ExtensionID extension, string itemString) => bool
+    {
+    my ($self, $extension, $itemString) = @_;
+
+    if (defined $self->[$extension])
+        {
+        if (exists $self->[$extension]->{$itemString})
+            {
+            delete $self->[$extension]->{$itemString};
+
+            if (!scalar keys %{$self->[$extension]})
+                {  $self->[$extension] = undef;  };
+
+            return 1;
+            };
+        };
+
+    return 0;
+    };
+
+
+#
+#   Function: HasDefinitions
+#
+#   Returns whether there are any definitions for this item.
+#
+sub HasDefinitions #(ExtensionID extension) => bool
+    {
+    my ($self, $extension) = @_;
+
+    return (defined $self->[$extension]);
+    };
+
+
+#
+#   Function: HasDefinition
+#
+#   Returns whether there is a definition for the passed item string.
+#
+sub HasDefinition #(ExtensionID extension, string itemString) => bool
+    {
+    my ($self, $extension, $itemString) = @_;
+
+    if (defined $self->[$extension])
+        {  return (exists $self->[$extension]->{$itemString});  }
+    else
+        {  return 0;  };
+    };
+
+
+1;

Added: trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/SourceDB.pm
===================================================================
--- trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/SourceDB.pm	                        (rev 0)
+++ trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/SourceDB.pm	2009-07-01 15:30:00 UTC (rev 4275)
@@ -0,0 +1,678 @@
+###############################################################################
+#
+#   Package: NaturalDocs::SourceDB
+#
+###############################################################################
+#
+#   SourceDB is an experimental package meant to unify the tracking of various elements in the source code.
+#
+#   Requirements:
+#
+#       - All extension packages must call <RegisterExtension()> before they can be used.
+#
+#
+#   Architecture: The Idea
+#
+#       For quite a while Natural Docs only needed <SymbolTable>.  However, 1.3 introduced the <ClassHierarchy> package
+#       which duplicated some of its functionality to track classes and parent references.  1.4 now needs <ImageReferenceTable>,
+#       so this package was an attempt to isolate the common functionality so the wheel doesn't have to keep being rewritten as
+#       the scope of Natural Docs expands.
+#
+#       SourceDB is designed around <Extensions> and items.  The purposefully vague "items" are anything in the source code
+#       that we need to track the definitions of.  Extensions are the packages to track them, only they're derived from
+#       <NaturalDocs::SourceDB::Extension> and registered with this package instead of being free standing and duplicating
+#       functionality such as watched files.
+#
+#       The architecture on this package isn't comprehensive yet.  As more extensions are added or previously made free standing
+#       packages are migrated to it it will expand to encompass them.  However, it's still experimental so this concept may
+#       eventually be abandoned for something better instead.
+#
+#
+#   Architecture: Assumptions
+#
+#       SourceDB is built around certain assumptions.
+#
+#       One item per file:
+#
+#           SourceDB assumes that only the first item per file with a particular item string is relevant.  For example, if two functions
+#           have the exact same name, there's no way to link to the second one either in HTML or internally so it doesn't matter for
+#           our purposes.  Likewise, if two references are exactly the same they go to the same target, so it doesn't matter whether
+#           there's one or two or a thousand.  All that matters is that at least one reference exists in this file because you only need
+#           to determine whether the entire file gets rebuilt.  If two items are different in some meaningful way, they should generate
+#           different item strings.
+#
+#       Watched file parsing:
+#
+#           SourceDB assumes the parse method is that the information that was stored from Natural Docs' previous run is loaded, a
+#           file is watched, that file is reparsed, and then <AnalyzeWatchedFileChanges()> is called.  When the file is reparsed all
+#           items within it are added the same as if the file was never parsed before.
+#
+#           If there's a new item this time around, that's fine no matter what.  However, a changed item wouldn't normally be
+#           recorded because the previous run's definition is seen as the first one and subsequent ones are ignored.  Also, deleted
+#           items would normally not be recorded either because we're only adding.
+#
+#           The watched file method fixes this because everything is also added to a second, clean database specifically for the
+#           watched file.  Because it starts clean, it always gets the first definition from the current parse which can then be
+#           compared to the original by <AnalyzeWatchedFileChanges()>.  Because it starts clean you can also compare it to the
+#           main database to see if anything was deleted, because it would appear in the main database but not the watched one.
+#
+#           This means that functions like <ChangeDefinition()> and <DeleteDefinition()> should only be called by
+#           <AnalyzeWatchedFileChanges()>.  Externally only <AddDefinition()> should be called.  <DeleteItem()> is okay to be
+#           called externally because entire items aren't managed by the watched file database, only definitions.
+#
+#
+###############################################################################
+
+# 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;
+
+
+use NaturalDocs::SourceDB::Extension;
+use NaturalDocs::SourceDB::Item;
+use NaturalDocs::SourceDB::ItemDefinition;
+use NaturalDocs::SourceDB::File;
+use NaturalDocs::SourceDB::WatchedFileDefinitions;
+
+
+package NaturalDocs::SourceDB;
+
+
+###############################################################################
+# Group: Types
+
+
+#
+#   Type: ExtensionID
+#
+#   A unique identifier for each <NaturalDocs::SourceDB> extension as given out by <RegisterExtension()>.
+#
+
+
+
+###############################################################################
+# Group: Variables
+
+
+#
+#   array: extensions
+#
+#   An array of <NaturalDocs::SourceDB::Extension>-derived extensions, as added with <RegisterExtension()>.  The indexes
+#   are the <ExtensionIDs> and the values are package references.
+#
+my @extensions;
+
+#
+#   array: extensionUsesDefinitionObjects
+#
+#   An array where the indexes are <ExtensionIDs> and the values are whether that extension uses its own definition class
+#   derived from <NaturalDocs::SourceDB::ItemDefinition> or it just tracks their existence.
+#
+my @extensionUsesDefinitionObjects;
+
+
+
+#
+#   array: items
+#
+#   The array of source items.  The <ExtensionIDs> are the indexes, and the values are hashrefs mapping the item
+#   string to <NaturalDocs::SourceDB::Item>-derived objects.  Hashrefs may be undef.
+#
+my @items;
+
+
+#
+#   hash: files
+#
+#   A hashref mapping source <FileNames> to <NaturalDocs::SourceDB::Files>.
+#
+my %files;
+
+
+#
+#   object: watchedFile
+#
+#   When a file is being watched for changes, will be a <NaturalDocs::SourceDB::File> for that file.  Is undef otherwise.
+#
+#   When the file is parsed, items are added to both this and the version in <files>.  Thus afterwards we can compare the two to
+#   see if any were deleted since the last time Natural Docs was run, because they would be in the <files> version but not this
+#   one.
+#
+my $watchedFile;
+
+
+#
+#   string: watchedFileName
+#
+#   When a file is being watched for changes, will be the <FileName> of the file being watched.  Is undef otherwise.
+#
+my $watchedFileName;
+
+
+#
+#   object: watchedFileDefinitions
+#
+#   When a file is being watched for changes, will be a <NaturalDocs::SourceDB::WatchedFileDefinitions> object.  Is undef
+#   otherwise.
+#
+#   When the file is parsed, items are added to both this and the version in <items>.  Since only the first definition is kept, this
+#   will always have the definition info from the file whereas the version in <items> will have the first definition as of the last time
+#   Natural Docs was run.  Thus they can be compared to see if the definitions of items that existed the last time around have
+#   changed.
+#
+my $watchedFileDefinitions;
+
+
+
+###############################################################################
+# Group: Extension Functions
+
+
+#
+#   Function: RegisterExtension
+#
+#   Registers a <NaturalDocs::SourceDB::Extension>-derived package and returns a unique <ExtensionID> for it.  All extensions
+#   must call this before they can be used.
+#
+#   Registration Order:
+#
+#       The order in which extensions register is important.  Whenever possible, items are added in the order their extensions
+#       registered.  However, items are changed and deleted in the reverse order.  Take advantage of this to minimize
+#       churn between extensions that are dependent on each other.
+#
+#       For example, when symbols are added or deleted they may cause references to be retargeted and thus their files need to
+#       be rebuilt.  However, adding or deleting references never causes the symbols' files to be rebuilt.  So it makes sense that
+#       symbols should be created before references, and that references should be deleted before symbols.
+#
+#   Parameters:
+#
+#       extension - The package or object of the extension.  Must be derived from <NaturalDocs::SourceDB::Extension>.
+#       usesDefinitionObjects - Whether the extension uses its own class derived from <NaturalDocs::SourceDB::ItemDefinition>
+#                                         or simply tracks each definitions existence.
+#
+#   Returns:
+#
+#       An <ExtensionID> unique to the extension.  This should be saved because it's required in functions such as <AddItem()>.
+#
+sub RegisterExtension #(package extension, bool usesDefinitionObjects) => ExtensionID
+    {
+    my ($self, $extension, $usesDefinitionObjects) = @_;
+
+    push @extensions, $extension;
+    push @extensionUsesDefinitionObjects, $usesDefinitionObjects;
+
+    return scalar @extensions - 1;
+    };
+
+
+
+
+###############################################################################
+# Group: File Functions
+
+
+#
+#   Function: Load
+#
+#   Loads the data of the source database and all the extensions.  Will call <NaturalDocs::SourceDB::Extension->Load()> for
+#   all of them, unless there's a situation where all the source files are going to be reparsed anyway in which case it's not needed.
+#
+sub Load
+    {
+    my $self = shift;
+
+    # No point loading if RebuildData is set.
+    if (!NaturalDocs::Settings->RebuildData())
+        {
+        # If any load fails, stop loading the rest and just reparse all the source files.
+        my $success = 1;
+
+        for (my $extension = 0; $extension < scalar @extensions && $success; $extension++)
+            {
+            $success = $extensions[$extension]->Load();
+            };
+
+        if (!$success)
+            {  NaturalDocs::Project->ReparseEverything();  };
+        };
+    };
+
+
+#
+#   Function: Save
+#
+#   Saves the data of the source database and all its extensions.  Will call <NaturalDocs::SourceDB::Extension->Save()> for all
+#   of them.
+#
+sub Save
+    {
+    my $self = shift;
+
+    for (my $extension = scalar @extensions - 1; $extension >= 0; $extension--)
+        {
+        $extensions[$extension]->Save();
+        };
+    };
+
+
+#
+#   Function: PurgeDeletedSourceFiles
+#
+#   Removes all data associated with deleted source files.
+#
+sub PurgeDeletedSourceFiles
+    {
+    my $self = shift;
+
+    my $filesToPurge = NaturalDocs::Project->FilesToPurge();
+
+    # Extension is the outermost loop because we want the extensions added last to have their definitions removed first to cause
+    # the least amount of churn between interdependent extensions.
+    for (my $extension = scalar @extensions - 1; $extension >= 0; $extension--)
+        {
+        foreach my $file (keys %$filesToPurge)
+            {
+            if (exists $files{$file})
+                {
+                my @items = $files{$file}->ListItems($extension);
+
+                foreach my $item (@items)
+                    {
+                    $self->DeleteDefinition($extension, $item, $file);
+                    };
+                }; # file exists
+            }; # each file
+        }; # each extension
+    };
+
+
+
+
+
+###############################################################################
+# Group: Item Functions
+
+
+#
+#   Function: AddItem
+#
+#   Adds the passed item to the database.  This will not work if the item string already exists.  The item added should *not*
+#   already have definitions attached.  Only use this to add blank items and then call <AddDefinition()> instead.
+#
+#   Parameters:
+#
+#       extension - An <ExtensionID>.
+#       itemString - The string serving as the item identifier.
+#       item - An object derived from <NaturalDocs::SourceDB::Item>.
+#
+#   Returns:
+#
+#       Whether the item was added, that is, whether it was the first time this item was added.
+#
+sub AddItem #(ExtensionID extension, string itemString, NaturalDocs::SourceDB::Item item) => bool
+    {
+    my ($self, $extension, $itemString, $item) = @_;
+
+    if (!defined $items[$extension])
+        {  $items[$extension] = { };  };
+
+    if (!exists $items[$extension]->{$itemString})
+        {
+        if ($item->HasDefinitions())
+            {  die "Tried to add an item to SourceDB that already had definitions.";  };
+
+        $items[$extension]->{$itemString} = $item;
+        return 1;
+        };
+
+    return 0;
+    };
+
+
+#
+#   Function: GetItem
+#
+#   Returns the <NaturalDocs::SourceDB::Item>-derived object for the passed <ExtensionID> and item string, or undef if there
+#   is none.
+#
+sub GetItem #(ExtensionID extension, string itemString) => bool
+    {
+    my ($self, $extensionID, $itemString) = @_;
+
+    if (defined $items[$extensionID])
+        {  return $items[$extensionID]->{$itemString};  }
+    else
+        {  return undef;  };
+    };
+
+
+#
+#   Function: DeleteItem
+#
+#   Deletes the record of the passed <ExtensionID> and item string.  Do *not* delete items that still have definitions.  Use
+#   <DeleteDefinition()> first.
+#
+#   Parameters:
+#
+#       extension - The <ExtensionID>.
+#       itemString - The item's identifying string.
+#
+#   Returns:
+#
+#       Whether it was successful, meaning whether an entry existed for it.
+#
+sub DeleteItem #(ExtensionID extension, string itemString) => bool
+    {
+    my ($self, $extension, $itemString) = @_;
+
+    if (defined $items[$extension] && exists $items[$extension]->{$itemString})
+        {
+        if ($items[$extension]->{$itemString}->HasDefinitions())
+            {  die "Tried to delete an item from SourceDB that still has definitions.";  };
+
+        delete $items[$extension]->{$itemString};
+        return 1;
+        }
+    else
+        {  return 0;  };
+    };
+
+
+#
+#   Function: HasItem
+#
+#   Returns whether there is an item defined for the passed <ExtensionID> and item string.
+#
+sub HasItem #(ExtensionID extension, string itemString) => bool
+    {
+    my ($self, $extension, $itemString) = @_;
+
+    if (defined $items[$extension])
+        {  return (exists $items[$extension]->{$itemString});  }
+    else
+        {  return 0;  };
+    };
+
+
+#
+#   Function: GetAllItemsHashRef
+#
+#   Returns a hashref of all the items defined for an extension.  *Do not change the contents.*  The keys are the item strings and
+#   the values are <NaturalDocs::SourceDB::Items> or derived classes.
+#
+sub GetAllItemsHashRef #(ExtensionID extension) => hashref
+    {
+    my ($self, $extension) = @_;
+    return $items[$extension];
+    };
+
+
+
+###############################################################################
+# Group: Definition Functions
+
+
+#
+#   Function: AddDefinition
+#
+#   Adds a definition to an item.  Assumes the item was already created with <AddItem()>.  If there's already a definition for this
+#   file in the item, the new definition will be ignored.
+#
+#   Parameters:
+#
+#       extension - The <ExtensionID>.
+#       itemString - The item string.
+#       file - The <FileName> the definition is in.
+#       definition - If you're using a custom <NaturalDocs::SourceDB::ItemDefinition> class, you must include an object for it here.
+#                       Otherwise this parameter is ignored.
+#
+#   Returns:
+#
+#       Whether the definition was added, which is to say, whether this was the first definition for the passed <FileName>.
+#
+sub AddDefinition #(ExtensionID extension, string itemString, FileName file, optional NaturalDocs::SourceDB::ItemDefinition definition) => bool
+    {
+    my ($self, $extension, $itemString, $file, $definition) = @_;
+
+
+    # Items
+
+    my $item = $self->GetItem($extension, $itemString);
+
+    if (!defined $item)
+        {  die "Tried to add a definition to an undefined item in SourceDB.";  };
+
+    if (!$extensionUsesDefinitionObjects[$extension])
+        {  $definition = 1;  };
+
+    my $result = $item->AddDefinition($file, $definition);
+
+
+    # Files
+
+    if (!exists $files{$file})
+        {  $files{$file} = NaturalDocs::SourceDB::File->New();  };
+
+    $files{$file}->AddItem($extension, $itemString);
+
+
+    # Watched File
+
+    if ($self->WatchingFileForChanges())
+        {
+        $watchedFile->AddItem($extension, $itemString);
+
+        if ($extensionUsesDefinitionObjects[$extension])
+            {  $watchedFileDefinitions->AddDefinition($extension, $itemString, $definition);  };
+        };
+
+
+    return $result;
+    };
+
+
+#
+#   Function: ChangeDefinition
+#
+#   Changes the definition of an item.  This function is only used for extensions that use custom
+#   <NaturalDocs::SourceDB::ItemDefinition>-derived classes.
+#
+#   Parameters:
+#
+#       extension - The <ExtensionID>.
+#       itemString - The item string.
+#       file - The <FileName> the definition is in.
+#       definition - The definition, which must be an object derived from <NaturalDocs::SourceDB::ItemDefinition>.
+#
+sub ChangeDefinition #(ExtensionID extension, string itemString, FileName file, NaturalDocs::SourceDB::ItemDefinition definition)
+    {
+    my ($self, $extension, $itemString, $file, $definition) = @_;
+
+    my $item = $self->GetItem($extension, $itemString);
+
+    if (!defined $item)
+        {  die "Tried to change the definition of an undefined item in SourceDB.";  };
+
+    if (!$extensionUsesDefinitionObjects[$extension])
+        {  die "Tried to change the definition of an item in an extension that doesn't use definition objects in SourceDB.";  };
+
+    if (!$item->HasDefinition($file))
+        {  die "Tried to change a definition that doesn't exist in SourceDB.";  };
+
+    $item->ChangeDefinition($file, $definition);
+    $extensions[$extension]->OnChangedDefinition($itemString, $file);
+    };
+
+
+#
+#   Function: GetDefinition
+#
+#   If the extension uses custom <NaturalDocs::SourceDB::ItemDefinition> classes, returns it for the passed definition or undef
+#   if it doesn't exist.  Otherwise returns whether it exists.
+#
+sub GetDefinition #(ExtensionID extension, string itemString, FileName file) => NaturalDocs::SourceDB::ItemDefinition or bool
+    {
+    my ($self, $extension, $itemString, $file) = @_;
+
+    my $item = $self->GetItem($extension, $itemString);
+
+    if (!defined $item)
+        {  return undef;  };
+
+    return $item->GetDefinition($file);
+    };
+
+
+#
+#   Function: DeleteDefinition
+#
+#   Removes the definition for the passed item.  Returns whether it was successful, meaning whether a definition existed for that
+#   file.
+#
+sub DeleteDefinition #(ExtensionID extension, string itemString, FileName file) => bool
+    {
+    my ($self, $extension, $itemString, $file) = @_;
+
+    my $item = $self->GetItem($extension, $itemString);
+
+    if (!defined $item)
+        {  return 0;  };
+
+    my $result = $item->DeleteDefinition($file);
+
+    if ($result)
+        {
+        $files{$file}->DeleteItem($extension, $itemString);
+        $extensions[$extension]->OnDeletedDefinition($itemString, $file, !$item->HasDefinitions());
+        };
+
+    return $result;
+    };
+
+
+#
+#   Function: HasDefinitions
+#
+#   Returns whether there are any definitions for this item.
+#
+sub HasDefinitions #(ExtensionID extension, string itemString) => bool
+    {
+    my ($self, $extension, $itemString) = @_;
+
+    my $item = $self->GetItem($extension, $itemString);
+
+    if (!defined $item)
+        {  return 0;  };
+
+    return $item->HasDefinitions();
+    };
+
+
+#
+#   Function: HasDefinition
+#
+#   Returns whether there is a definition for the passed <FileName>.
+#
+sub HasDefinition #(ExtensionID extension, string itemString, FileName file) => bool
+    {
+    my ($self, $extension, $itemString, $file) = @_;
+
+    my $item = $self->GetItem($extension, $itemString);
+
+    if (!defined $item)
+        {  return 0;  };
+
+    return $item->HasDefinition($file);
+    };
+
+
+
+###############################################################################
+# Group: Watched File Functions
+
+
+#
+#   Function: WatchFileForChanges
+#
+#   Begins watching a file for changes.  Only one file at a time can be watched.
+#
+#   This should be called before a file is parsed so the file info goes both into the main database and the watched file info.
+#   Afterwards you call <AnalyzeWatchedFileChanges()> so item deletions and definition changes can be detected.
+#
+#   Parameters:
+#
+#       filename - The <FileName> to watch.
+#
+sub WatchFileForChanges #(FileName filename)
+    {
+    my ($self, $filename) = @_;
+
+    $watchedFileName = $filename;
+    $watchedFile = NaturalDocs::SourceDB::File->New();
+    $watchedFileDefinitions = NaturalDocs::SourceDB::WatchedFileDefinitions->New();
+    };
+
+
+#
+#   Function: WatchingFileForChanges
+#
+#   Returns whether we're currently watching a file for changes or not.
+#
+sub WatchingFileForChanges # => bool
+    {
+    my $self = shift;
+    return defined $watchedFileName;
+    };
+
+
+#
+#   Function: AnalyzeWatchedFileChanges
+#
+#   Analyzes the watched file for changes.  Will delete and change definitions as necessary.
+#
+sub AnalyzeWatchedFileChanges
+    {
+    my $self = shift;
+
+    if (!$self->WatchingFileForChanges())
+        {  die "Tried to analyze watched file for changes in SourceDB when no file was being watched.";  };
+    if (!$files{$watchedFileName})
+        {  return;  };
+
+
+    # Process extensions last registered to first.
+
+    for (my $extension = scalar @extensions - 1; $extension >= 0; $extension--)
+        {
+        my @items = $files{$watchedFileName}->ListItems($extension);
+
+        foreach my $item (@items)
+            {
+            if ($watchedFile->HasItem($extension, $item))
+                {
+                if ($extensionUsesDefinitionObjects[$extension])
+                    {
+                    my $originalDefinition = $items[$extension]->GetDefinition($watchedFileName);
+                    my $watchedDefinition = $watchedFileDefinitions->GetDefinition($extension, $item);
+
+                    if (!$originalDefinition->Compare($watchedDefinition))
+                        {  $self->ChangeDefinition($extension, $item, $watchedFileName, $watchedDefinition);  };
+                    }
+                }
+            else # !$watchedFile->HasItem($item)
+                {
+                $self->DeleteDefinition($extension, $item, $watchedFileName);
+                };
+            };
+        };
+
+
+    $watchedFile = undef;
+    $watchedFileName = undef;
+    $watchedFileDefinitions = undef;
+    };
+
+
+1;

Added: trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/StatusMessage.pm
===================================================================
--- trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/StatusMessage.pm	                        (rev 0)
+++ trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/StatusMessage.pm	2009-07-01 15:30:00 UTC (rev 4275)
@@ -0,0 +1,102 @@
+###############################################################################
+#
+#   Package: NaturalDocs::StatusMessage
+#
+###############################################################################
+#
+#   A package to handle status message updates.  Automatically handles <NaturalDocs::Settings->IsQuiet()>.
+#
+###############################################################################
+
+# 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::StatusMessage;
+
+
+#
+#   var: message
+#   The message to display.
+#
+my $message;
+
+#
+#   var: total
+#   The number of items to work through.
+#
+my $total;
+
+#
+#   var: completed
+#   The number of items completed.
+#
+my $completed;
+
+#
+#   var: lastMessageTime
+#   The time the last message was posted.
+#
+my $lastMessageTime;
+
+
+#
+#   constant: TIME_BETWEEN_UPDATES
+#   The number of seconds that should occur between updates.
+#
+use constant TIME_BETWEEN_UPDATES => 10;
+
+
+
+#
+#   Function: Start
+#
+#   Starts the status message.
+#
+#   Parameters:
+#
+#       message - The message to post.
+#       total - The number of items that are going to be worked through.
+#
+sub Start #(message, total)
+    {
+    my $self = shift;
+
+    if (!NaturalDocs::Settings->IsQuiet())
+        {
+        ($message, $total) = @_;
+        $completed = 0;
+
+        print $message . "\n";
+
+        $lastMessageTime = time();
+        };
+    };
+
+
+#
+#   Function: CompletedItem
+#
+#   Should be called every time an item is completed.
+#
+sub CompletedItem
+    {
+    my $self = shift;
+
+    if (!NaturalDocs::Settings->IsQuiet())
+        {
+        # We scale completed by 100 since we need to anyway to get the percentage.
+
+        $completed += 100;
+
+        if (time() >= $lastMessageTime + TIME_BETWEEN_UPDATES && $completed != $total * 100)
+            {
+            print $message . ' (' . ($completed / $total) . '%)' . "\n";
+            $lastMessageTime = time();
+            };
+        };
+    };
+
+1;

Added: trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/SymbolString.pm
===================================================================
--- trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/SymbolString.pm	                        (rev 0)
+++ trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/SymbolString.pm	2009-07-01 15:30:00 UTC (rev 4275)
@@ -0,0 +1,212 @@
+###############################################################################
+#
+#   Package: NaturalDocs::SymbolString
+#
+###############################################################################
+#
+#   A package to manage <SymbolString> handling throughout the program.
+#
+###############################################################################
+
+# 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::SymbolString;
+
+
+#
+#   Function: FromText
+#
+#   Extracts and returns a <SymbolString> from plain text.
+#
+#   This should be the only way to get a <SymbolString> from plain text, as the splitting and normalization must be consistent
+#   throughout the application.
+#
+sub FromText #(string textSymbol)
+    {
+    my ($self, $textSymbol) = @_;
+
+    # The internal format of a symbol is all the normalized identifiers separated by 0x1F characters.
+
+    # Convert whitespace and reserved characters to spaces, and condense multiple consecutive ones.
+    $textSymbol =~ tr/ \t\r\n\x1C\x1D\x1E\x1F/ /s;
+
+    # DEPENDENCY: ReferenceString->MakeFrom() assumes all 0x1E characters were removed.
+    # DEPENDENCY: ReferenceString->MakeFrom() assumes this encoding doesn't use 0x1E characters.
+
+    # Remove spaces unless they're separating two alphanumeric/underscore characters.
+    $textSymbol =~ s/^ //;
+    $textSymbol =~ s/ $//;
+    $textSymbol =~ s/(\W) /$1/g;
+    $textSymbol =~ s/ (\W)/$1/g;
+
+    # Remove trailing empty parenthesis, so Function and Function() are equivalent.
+    $textSymbol =~ s/\(\)$//;
+
+    # Split the string into pieces.
+    my @pieces = split(/(\.|::|->)/, $textSymbol);
+    my $symbolString;
+
+    my $lastWasSeparator = 1;
+
+    foreach my $piece (@pieces)
+        {
+        if ($piece =~ /^(?:\.|::|->)$/)
+            {
+            if (!$lastWasSeparator)
+                {
+                $symbolString .= "\x1F";
+                $lastWasSeparator = 1;
+                };
+            }
+        elsif (length $piece)
+            {
+            $symbolString .= $piece;
+            $lastWasSeparator = 0;
+            };
+        # Ignore empty pieces
+        };
+
+    $symbolString =~ s/\x1F$//;
+
+    return $symbolString;
+    };
+
+
+#
+#   Function: ToText
+#
+#   Converts a <SymbolString> to text, using the passed separator.
+#
+sub ToText #(SymbolString symbolString, string separator)
+    {
+    my ($self, $symbolString, $separator) = @_;
+
+    my @identifiers = $self->IdentifiersOf($symbolString);
+    return join($separator, @identifiers);
+    };
+
+
+#
+#   Function: ToBinaryFile
+#
+#   Writes a <SymbolString> to the passed filehandle.  Can also encode an undef.
+#
+#   Parameters:
+#
+#       fileHandle - The filehandle to write to.
+#       symbol - The <SymbolString> to write, or undef.
+#
+#   Format:
+#
+#       > [UInt8: number of identifiers]
+#       >    [AString16: identifier] [AString16: identifier] ...
+#
+#       Undef is represented by a zero for the number of identifiers.
+#
+sub ToBinaryFile #(FileHandle fileHandle, SymbolString symbol)
+    {
+    my ($self, $fileHandle, $symbol) = @_;
+
+    my @identifiers;
+    if (defined $symbol)
+        {  @identifiers = $self->IdentifiersOf($symbol);  };
+
+    print $fileHandle pack('C', scalar @identifiers);
+
+    foreach my $identifier (@identifiers)
+        {
+        print $fileHandle pack('nA*', length($identifier), $identifier);
+        };
+    };
+
+
+#
+#   Function: FromBinaryFile
+#
+#   Loads a <SymbolString> or undef from the filehandle and returns it.
+#
+#   Parameters:
+#
+#       fileHandle - The filehandle to read from.
+#
+#   Returns:
+#
+#       The <SymbolString> or undef.
+#
+#   See also:
+#
+#       See <ToBinaryFile()> for format and dependencies.
+#
+sub FromBinaryFile #(FileHandle fileHandle)
+    {
+    my ($self, $fileHandle) = @_;
+
+    my $raw;
+
+    # [UInt8: number of identifiers or 0 if none]
+
+    read($fileHandle, $raw, 1);
+    my $identifierCount = unpack('C', $raw);
+
+    my @identifiers;
+
+    while ($identifierCount)
+        {
+        # [AString16: identifier] [AString16: identifier] ...
+
+        read($fileHandle, $raw, 2);
+        my $identifierLength = unpack('n', $raw);
+
+        my $identifier;
+        read($fileHandle, $identifier, $identifierLength);
+
+        push @identifiers, $identifier;
+
+        $identifierCount--;
+        };
+
+    if (scalar @identifiers)
+        {  return $self->Join(@identifiers);  }
+    else
+        {  return undef;  };
+    };
+
+
+#
+#   Function: IdentifiersOf
+#
+#   Returns the <SymbolString> as an array of identifiers.
+#
+sub IdentifiersOf #(SymbolString symbol)
+    {
+    my ($self, $symbol) = @_;
+    return split(/\x1F/, $symbol);
+    };
+
+
+#
+#   Function: Join
+#
+#   Takes a list of identifiers and/or <SymbolStrings> and returns it as a new <SymbolString>.
+#
+sub Join #(string/SymbolString identifier/symbol, string/SymolString identifier/symbol, ...)
+    {
+    my ($self, @pieces) = @_;
+
+    # Can't have undefs screwing everything up.
+    while (scalar @pieces && !defined $pieces[0])
+        {  shift @pieces;  };
+
+    # We need to test @pieces first because joining on an empty array returns an empty string rather than undef.
+    if (scalar @pieces)
+       {  return join("\x1F", @pieces);  }
+    else
+        {  return undef;  };
+    };
+
+
+1;

Added: trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/SymbolTable/File.pm
===================================================================
--- trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/SymbolTable/File.pm	                        (rev 0)
+++ trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/SymbolTable/File.pm	2009-07-01 15:30:00 UTC (rev 4275)
@@ -0,0 +1,186 @@
+###############################################################################
+#
+#   Package: NaturalDocs::SymbolTable::File
+#
+###############################################################################
+#
+#   A class representing a file, keeping track of what symbols and references are defined in it.
+#
+###############################################################################
+
+# 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::SymbolTable::File;
+
+
+###############################################################################
+# Group: Implementation
+
+#
+#   Constants: Members
+#
+#   The class is implemented as a blessed arrayref.  The following constants are its members.
+#
+#       SYMBOLS       - An existence hashref of the <SymbolStrings> it defines.
+#       REFERENCES  - An existence hashref of the <ReferenceStrings> in the file.
+#
+
+# DEPENDENCY: New() depends on the order of these constants.  If they change, New() has to be updated.
+use constant SYMBOLS => 0;
+use constant REFERENCES => 1;
+
+
+###############################################################################
+# Group: Modification Functions
+
+
+#
+#   Function: New
+#
+#   Creates and returns a new object.
+#
+sub New
+    {
+    my $package = shift;
+
+    # Let's make it safe, since normally you can pass values to New.  Having them just be ignored would be an obscure error.
+    if (scalar @_)
+        {  die "You can't pass values to NaturalDocs::SymbolTable::File->New()\n";  };
+
+    # DEPENDENCY: This code depends on the order of the member constants.
+    my $object = [ { }, { } ];
+    bless $object, $package;
+
+    return $object;
+    };
+
+
+#
+#   Function: AddSymbol
+#
+#   Adds a <SymbolString> definition.
+#
+#   Parameters:
+#
+#       symbol - The <SymbolString> being added.
+#
+sub AddSymbol #(symbol)
+    {
+    my ($self, $symbol) = @_;
+    $self->[SYMBOLS]{$symbol} = 1;
+    };
+
+
+#
+#   Function: DeleteSymbol
+#
+#   Removes a <SymbolString> definition.
+#
+#   Parameters:
+#
+#       symbol - The <SymbolString> to delete.
+#
+sub DeleteSymbol #(symbol)
+    {
+    my ($self, $symbol) = @_;
+    delete $self->[SYMBOLS]{$symbol};
+    };
+
+
+#
+#   Function: AddReference
+#
+#   Adds a reference definition.
+#
+#   Parameters:
+#
+#       referenceString - The <ReferenceString> being added.
+#
+sub AddReference #(referenceString)
+    {
+    my ($self, $referenceString) = @_;
+    $self->[REFERENCES]{$referenceString} = 1;
+    };
+
+
+#
+#   Function: DeleteReference
+#
+#   Removes a reference definition.
+#
+#   Parameters:
+#
+#       referenceString - The <ReferenceString> to delete.
+#
+sub DeleteReference #(referenceString)
+    {
+    my ($self, $referenceString) = @_;
+    delete $self->[REFERENCES]{$referenceString};
+    };
+
+
+
+###############################################################################
+# Group: Information Functions
+
+
+#
+#   Function: HasAnything
+#
+#   Returns whether the file has any symbol or reference definitions at all.
+#
+sub HasAnything
+    {
+    return (scalar keys %{$_[0]->[SYMBOLS]} || scalar keys %{$_[0]->[REFERENCES]});
+    };
+
+#
+#   Function: Symbols
+#
+#   Returns an array of all the <SymbolStrings> defined in this file.  If none, returns an empty array.
+#
+sub Symbols
+    {
+    return keys %{$_[0]->[SYMBOLS]};
+    };
+
+
+#
+#   Function: References
+#
+#   Returns an array of all the <ReferenceStrings> defined in this file.  If none, returns an empty array.
+#
+sub References
+    {
+    return keys %{$_[0]->[REFERENCES]};
+    };
+
+
+#
+#   Function: DefinesSymbol
+#
+#   Returns whether the file defines the passed <SymbolString> or not.
+#
+sub DefinesSymbol #(symbol)
+    {
+    my ($self, $symbol) = @_;
+    return exists $self->[SYMBOLS]{$symbol};
+    };
+
+
+#
+#   Function: DefinesReference
+#
+#   Returns whether the file defines the passed <ReferenceString> or not.
+#
+sub DefinesReference #(referenceString)
+    {
+    my ($self, $referenceString) = @_;
+    return exists $self->[REFERENCES]{$referenceString};
+    };
+
+1;

Added: trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/SymbolTable/IndexElement.pm
===================================================================
--- trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/SymbolTable/IndexElement.pm	                        (rev 0)
+++ trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/SymbolTable/IndexElement.pm	2009-07-01 15:30:00 UTC (rev 4275)
@@ -0,0 +1,522 @@
+###############################################################################
+#
+#   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 <File()>, <Type()>, or <Prototype()>; those will be defined in child elements.  Similarly, the child elements will
+#   not define <Symbol()> 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 <HasMultiplePackages()> and
+#   <HasMultipleFiles()> to determine whether you need to add a subindex for it.
+#
+#
+#   Combining Properties:
+#
+#   All IndexElements also have combining properties set.
+#
+#   CombinedType - The general <TopicType> of the entry.  Conflicts combine into <TOPIC_GENERAL>.
+#   PackageSeparator - The package separator symbol of the entry.  Conflicts combine into a dot.
+#
+#   So if an IndexElement only has one definition, <CombinedType()> is the same as the <TopicType> and <PackageSeparator()>
+#   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:
+#
+#   <SortableSymbol()> 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 <SymbolString> without the package portion.
+#   PACKAGE - The package <SymbolString>.  Will be a package <SymbolString>, undef for global, or an arrayref of
+#                    <NaturalDocs::SymbolTable::IndexElement> objects if multiple packages define the symbol.
+#   FILE - The <FileName> the package/symbol is defined in.  Will be the file name or an arrayref of
+#            <NaturalDocs::SymbolTable::IndexElements> if multiple files define the package/symbol.
+#   TYPE - The package/symbol/file <TopicType>.
+#   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 <TopicType> 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 <Merge()> instead.
+#
+#   Parameters:
+#
+#       symbol  - The <SymbolString> without the package portion.
+#       package - The package <SymbolString>, or undef for global.
+#       file  - The symbol's definition file.
+#       type  - The symbol's <TopicType>.
+#       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 <TopicType>.
+#       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 <SymbolString>, or undef for global.
+#       file  - The symbol's definition file.
+#       type  - The symbol's <TopicType>.
+#       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 <SortableSymbol()> and <IgnoredPrefix()>.  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 <SymbolString> without the package portion.
+#   Package - If <HasMultiplePackages()> is true, returns an arrayref of <NaturalDocs::SymbolTable::IndexElement> objects.
+#                  Otherwise returns the package <SymbolString>, or undef if global.
+#   File - If <HasMultipleFiles()> is true, returns an arrayref of <NaturalDocs::SymbolTable::IndexElement> objects.  Otherwise
+#           returns the name of the definition file.
+#   Type - Returns the <TopicType> 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 <TopicType> 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 <MakeSortableSymbol()>.
+#   IgnoredPrefix - Returns the part of the symbol that was stripped off to make the <SortableSymbol()>, or undef if none.
+#                          Only available after calling <MakeSortableSymbol()>.
+#
+
+#   Function: HasMultiplePackages
+#   Returns whether <Packages()> is broken out into more elements.
+sub HasMultiplePackages
+    {  return ref($_[0]->[PACKAGE]);  };
+
+#   Function: HasMultipleFiles
+#   Returns whether <File()> 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 <TopicType>.
+#       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;

Added: trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/SymbolTable/Reference.pm
===================================================================
--- trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/SymbolTable/Reference.pm	                        (rev 0)
+++ trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/SymbolTable/Reference.pm	2009-07-01 15:30:00 UTC (rev 4275)
@@ -0,0 +1,273 @@
+###############################################################################
+#
+#   Package: NaturalDocs::SymbolTable::Reference
+#
+###############################################################################
+#
+#   A class representing a symbol or a potential symbol.
+#
+###############################################################################
+
+# 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::SymbolTable::Reference;
+
+
+###############################################################################
+# Group: Implementation
+
+#
+#   Constants: Members
+#
+#   The class is implemented as a blessed arrayref.  The following constants are its members.
+#
+#       DEFINITIONS                        - An existence hashref of the <FileNames> that define this reference.
+#       INTERPRETATIONS                - A hashref of the possible interpretations of this reference.  The keys are the <SymbolStrings>
+#                                                     and the values are the scores.
+#       CURRENT_INTERPRETATION  - The interpretation currently used as the reference target.  It will be the interpretation with
+#                                                     the highest score that is actually defined.  If none are defined, this item will be undef.
+#
+
+# DEPENDENCY: New() depends on the order of these constants.  If they change, New() has to be updated.
+use constant DEFINITIONS => 0;
+use constant INTERPRETATIONS => 1;
+use constant CURRENT_INTERPRETATION => 2;
+
+
+###############################################################################
+# Group: Modification Functions
+
+
+#
+#   Function: New
+#
+#   Creates and returns a new object.
+#
+sub New
+    {
+    my $package = shift;
+
+    # Let's make it safe, since normally you can pass values to New.  Having them just be ignored would be an obscure error.
+    if (scalar @_)
+        {  die "You can't pass values to NaturalDocs::SymbolTable::Reference->New()\n";  };
+
+    # DEPENDENCY: This code depends on the order of the member constants.
+    my $object = [ { }, { }, undef ];
+    bless $object, $package;
+
+    return $object;
+    };
+
+
+#
+#   Function: AddDefinition
+#
+#   Adds a reference definition.
+#
+#   Parameters:
+#
+#       file   - The <FileName> that defines the reference.
+#
+sub AddDefinition #(file)
+    {
+    my ($self, $file) = @_;
+
+    $self->[DEFINITIONS]{$file} = 1;
+    };
+
+
+#
+#   Function: DeleteDefinition
+#
+#   Removes a reference definition.
+#
+#   Parameters:
+#
+#       file - The <FileName> which has the definition to delete.
+#
+sub DeleteDefinition #(file)
+    {
+    my ($self, $file) = @_;
+
+    delete $self->[DEFINITIONS]{$file};
+    };
+
+
+#
+#   Function: AddInterpretation
+#
+#   Adds a symbol that this reference can be interpreted as.
+#
+#   Parameters:
+#
+#       symbol  - The <SymbolString>.
+#       score     - The score of this interpretation.
+#
+sub AddInterpretation #(symbol, score)
+    {
+    my ($self, $symbol, $score) = @_;
+
+    $self->[INTERPRETATIONS]{$symbol} = $score;
+    };
+
+
+#
+#   Function: DeleteInterpretation
+#
+#   Deletes a symbol that this reference can be interpreted as.
+#
+#   Parameters:
+#
+#       symbol - The <SymbolString> to delete.
+#
+sub DeleteInterpretation #(symbol)
+    {
+    my ($self, $symbol) = @_;
+
+    delete $self->[INTERPRETATIONS]{$symbol};
+    };
+
+
+#
+#   Function: DeleteAllInterpretationsButCurrent
+#
+#   Deletes all interpretations except for the current one.
+#
+sub DeleteAllInterpretationsButCurrent
+    {
+    my $self = shift;
+
+    if ($self->HasCurrentInterpretation())
+        {
+        my $score = $self->CurrentScore();
+
+        # Fastest way to clear a hash except for one item?  Make a new hash with just that item.
+        %{$self->[INTERPRETATIONS]} = ( $self->[CURRENT_INTERPRETATION] => $score );
+        };
+    };
+
+
+#
+#   Function: SetCurrentInterpretation
+#
+#   Changes the current interpretation.  The new one must already have been added via <AddInterpretation()>.
+#
+#   Parameters:
+#
+#       symbol - The <SymbolString>l to make the current interpretation.  Can be set to undef to clear it.
+#
+sub SetCurrentInterpretation #(symbol)
+    {
+    my ($self, $symbol) = @_;
+
+    $self->[CURRENT_INTERPRETATION] = $symbol;
+    };
+
+
+###############################################################################
+# Group: Information Functions
+
+
+#
+#   Function: Definitions
+#
+#   Returns an array of all the <FileNames> that define this reference.  If none do, returns an empty array.
+#
+sub Definitions
+    {
+    return keys %{$_[0]->[DEFINITIONS]};
+    };
+
+
+#
+#   Function: IsDefined
+#
+#   Returns whether the reference has any definitions or not.
+#
+sub IsDefined
+    {
+    return scalar keys %{$_[0]->[DEFINITIONS]};
+    };
+
+
+#
+#   Function: IsDefinedIn
+#
+#   Returns whether the reference is defined in the passed <FileName>.
+#
+sub IsDefinedIn #(file)
+    {
+    my ($self, $file) = @_;
+
+    return exists $self->[DEFINITIONS]{$file};
+    };
+
+
+#
+#   Function: Interpretations
+#
+#   Returns an array of all the <SymbolStrings> that this reference can be interpreted as.  If none, returns an empty array.
+#
+sub Interpretations
+    {
+    return keys %{$_[0]->[INTERPRETATIONS]};
+    };
+
+
+#
+#   Function: InterpretationsAndScores
+#
+#   Returns a hash of all the <SymbolStrings> that this reference can be interpreted as and their scores.  The keys are the <SymbolStrings>
+#   and the values are the scores.  If none, returns an empty hash.
+#
+sub InterpretationsAndScores
+    {
+    return %{$_[0]->[INTERPRETATIONS]};
+    };
+
+
+#
+#   Function: HasCurrentInterpretation
+#
+#   Returns whether the reference has a current interpretation or not.
+#
+sub HasCurrentInterpretation
+    {
+    return defined $_[0]->[CURRENT_INTERPRETATION];
+    };
+
+
+#
+#   Function: CurrentInterpretation
+#
+#   Returns the <SymbolString> of the current interpretation, or undef if none.
+#
+sub CurrentInterpretation
+    {
+    return $_[0]->[CURRENT_INTERPRETATION];
+    };
+
+
+#
+#   Function: CurrentScore
+#
+#   Returns the score of the current interpretation, or undef if none.
+#
+sub CurrentScore
+    {
+    my $self = shift;
+
+    if (defined $self->[CURRENT_INTERPRETATION])
+        {
+        return $self->[INTERPRETATIONS]{ $self->[CURRENT_INTERPRETATION] };
+        }
+    else
+        {  return undef;  };
+    };
+
+
+1;

Added: trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/SymbolTable/ReferenceTarget.pm
===================================================================
--- trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/SymbolTable/ReferenceTarget.pm	                        (rev 0)
+++ trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/SymbolTable/ReferenceTarget.pm	2009-07-01 15:30:00 UTC (rev 4275)
@@ -0,0 +1,97 @@
+###############################################################################
+#
+#   Class: NaturalDocs::SymbolTable::ReferenceTarget
+#
+###############################################################################
+#
+#   A class for storing information about a reference target.
+#
+###############################################################################
+
+# 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::SymbolTable::ReferenceTarget;
+
+
+###############################################################################
+# Group: Implementation
+
+#
+#   Constants: Members
+#
+#   The class is implemented as a blessed arrayref.  The following constants are its members.
+#
+#       SYMBOL  - The target <SymbolString>.
+#       FILE        - The <FileName> the target is defined in.
+#       TYPE       - The target <TopicType>.
+#       PROTOTYPE - The target's prototype, or undef if none.
+#       SUMMARY    - The target's summary, or undef if none.
+#
+
+# DEPENDENCY: New() depends on the order of these constants.  If they change, New() has to be updated.
+use constant SYMBOL => 0;
+use constant FILE => 1;
+use constant TYPE => 2;
+use constant PROTOTYPE => 3;
+use constant SUMMARY => 4;
+
+###############################################################################
+# Group: Functions
+
+
+#
+#   Function: New
+#
+#   Creates and returns a new object.
+#
+#   Parameters:
+#
+#       symbol - The target <SymbolString>.
+#       file       - The <FileName> the target is defined in.
+#       type     - The <TopicType> of the target symbol.
+#       prototype - The target's prototype.  Set to undef if not defined or not applicable.
+#       summary - The target's summary.  Set to undef if not defined or not applicable.
+#
+sub New #(symbol, file, type, prototype, summary)
+    {
+    # DEPENDENCY: This code depends on the order of the member constants.
+
+    my $package = shift;
+
+    my $object = [ @_ ];
+    bless $object, $package;
+
+    return $object;
+    };
+
+
+# Function: Symbol
+# Returns the target's <SymbolString>.
+sub Symbol
+    {  return $_[0]->[SYMBOL];  };
+
+# Function: File
+# Returns the <FileName> the target is defined in.
+sub File
+    {  return $_[0]->[FILE];  };
+
+# Function: Type
+# Returns the target's <TopicType>.
+sub Type
+    {  return $_[0]->[TYPE];  };
+
+# Function: Prototype
+# Returns the target's prototype, or undef if not defined or not applicable.
+sub Prototype
+    {  return $_[0]->[PROTOTYPE];  };
+
+# Function: Summary
+# Returns the target's summary, or undef if not defined or not applicable.
+sub Summary
+    {  return $_[0]->[SUMMARY];  };
+
+1;

Added: trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/SymbolTable/Symbol.pm
===================================================================
--- trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/SymbolTable/Symbol.pm	                        (rev 0)
+++ trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/SymbolTable/Symbol.pm	2009-07-01 15:30:00 UTC (rev 4275)
@@ -0,0 +1,428 @@
+###############################################################################
+#
+#   Package: NaturalDocs::SymbolTable::Symbol
+#
+###############################################################################
+#
+#   A class representing a symbol or a potential symbol.
+#
+###############################################################################
+
+# 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::SymbolTable::Symbol;
+
+
+###############################################################################
+# Group: Implementation
+
+#
+#   Constants: Members
+#
+#   The class is implemented as a blessed arrayref.  The following constants are its members.
+#
+#       DEFINITIONS             - A hashref of all the files which define this symbol.  The keys are the <FileNames>, and the values are
+#                                         <NaturalDocs::SymbolTable::SymbolDefinition> objects.  If no files define this symbol, this item will
+#                                          be undef.
+#       GLOBAL_DEFINITION  - The <FileName> which defines the global version of the symbol, which is what is used if
+#                                          a file references the symbol but does not have its own definition.  If there are no definitions, this
+#                                          item will be undef.
+#       REFERENCES              - A hashref of the references that can be interpreted as this symbol.  This doesn't mean these
+#                                          references necessarily are.  The keys are the reference strings, and the values are the scores of
+#                                          the interpretations.  If no references can be interpreted as this symbol, this item will be undef.
+#
+use constant DEFINITIONS => 0;
+use constant GLOBAL_DEFINITION => 1;
+use constant REFERENCES => 2;
+
+
+###############################################################################
+# Group: Modification Functions
+
+#
+#   Function: New
+#
+#   Creates and returns a new object.
+#
+sub New
+    {
+    my $package = shift;
+
+    # Let's make it safe, since normally you can pass values to New.  Having them just be ignored would be an obscure error.
+    if (scalar @_)
+        {  die "You can't pass values to NaturalDocs::SymbolTable::Symbol->New()\n";  };
+
+    my $object = [ undef, undef, undef ];
+    bless $object, $package;
+
+    return $object;
+    };
+
+#
+#   Function: AddDefinition
+#
+#   Adds a symbol definition.  If this is the first definition for this symbol, it will become the global definition.  If the definition
+#   already exists for the file, it will be ignored.
+#
+#   Parameters:
+#
+#       file   - The <FileName> that defines the symbol.
+#       type - The <TopicType> of the definition.
+#       prototype - The prototype of the definition, if applicable.  Undef otherwise.
+#       summary - The summary for the definition, if applicable.  Undef otherwise.
+#
+#   Returns:
+#
+#       Whether this provided the first definition for this symbol.
+#
+sub AddDefinition #(file, type, prototype, summary)
+    {
+    my ($self, $file, $type, $prototype, $summary) = @_;
+
+    my $isFirst;
+
+    if (!defined $self->[DEFINITIONS])
+        {
+        $self->[DEFINITIONS] = { };
+        $self->[GLOBAL_DEFINITION] = $file;
+        $isFirst = 1;
+        };
+
+    if (!exists $self->[DEFINITIONS]{$file})
+        {
+        $self->[DEFINITIONS]{$file} = NaturalDocs::SymbolTable::SymbolDefinition->New($type, $prototype, $summary);
+        };
+
+    return $isFirst;
+    };
+
+
+#
+#   Function: ChangeDefinition
+#
+#   Changes the information about an existing definition.
+#
+#   Parameters:
+#
+#       file   - The <FileName> that defines the symbol.  Must exist.
+#       type - The new <TopicType> of the definition.
+#       prototype - The new prototype of the definition, if applicable.  Undef otherwise.
+#       summary - The new summary of the definition, if applicable.  Undef otherwise.
+#
+sub ChangeDefinition #(file, type, prototype, summary)
+    {
+    my ($self, $file, $type, $prototype, $summary) = @_;
+
+    if (defined $self->[DEFINITIONS] &&
+        exists $self->[DEFINITIONS]{$file})
+        {
+        $self->[DEFINITIONS]{$file}->SetType($type);
+        $self->[DEFINITIONS]{$file}->SetPrototype($prototype);
+        $self->[DEFINITIONS]{$file}->SetSummary($summary);
+        };
+    };
+
+
+#
+#   Function: DeleteDefinition
+#
+#   Removes a symbol definition.  If the definition served as the global definition, a new one will be selected.
+#
+#   Parameters:
+#
+#       file - The <FileName> which contains definition to delete.
+#
+#   Returns:
+#
+#       Whether that was the only definition, and the symbol is now undefined.
+#
+sub DeleteDefinition #(file)
+    {
+    my ($self, $file) = @_;
+
+    # If there are no definitions...
+    if (!defined $self->[DEFINITIONS])
+        {  return undef;  };
+
+    delete $self->[DEFINITIONS]{$file};
+
+    # If there are no more definitions...
+    if (!scalar keys %{$self->[DEFINITIONS]})
+        {
+        $self->[DEFINITIONS] = undef;
+
+        # If definitions was previously defined, and now is empty, we can safely assume that the global definition was just deleted
+        # without checking it against $file.
+
+        $self->[GLOBAL_DEFINITION] = undef;
+
+        return 1;
+        }
+
+    # If there are more definitions and the global one was just deleted...
+    elsif ($self->[GLOBAL_DEFINITION] eq $file)
+        {
+        # Which one becomes global is pretty much random.
+        $self->[GLOBAL_DEFINITION] = (keys %{$self->[DEFINITIONS]})[0];
+        return undef;
+        };
+    };
+
+
+#
+#   Function: AddReference
+#
+#   Adds a reference that can be interpreted as this symbol.  It can be, but not necessarily is.
+#
+#   Parameters:
+#
+#       referenceString - The string of the reference.
+#       score                - The score of this interpretation.
+#
+sub AddReference #(referenceString, score)
+    {
+    my ($self, $referenceString, $score) = @_;
+
+    if (!defined $self->[REFERENCES])
+        {  $self->[REFERENCES] = { };  };
+
+    $self->[REFERENCES]{$referenceString} = $score;
+    };
+
+
+#
+#   Function: DeleteReference
+#
+#   Deletes a reference that can be interpreted as this symbol.
+#
+#   Parameters:
+#
+#       referenceString - The string of the reference to delete.
+#
+sub DeleteReference #(referenceString)
+    {
+    my ($self, $referenceString) = @_;
+
+    # If there are no definitions...
+    if (!defined $self->[REFERENCES])
+        {  return;  };
+
+    delete $self->[REFERENCES]{$referenceString};
+
+    # If there are no more definitions...
+    if (!scalar keys %{$self->[REFERENCES]})
+        {
+        $self->[REFERENCES] = undef;
+        };
+    };
+
+
+#
+#   Function: DeleteAllReferences
+#
+#   Removes all references that can be interpreted as this symbol.
+#
+sub DeleteAllReferences
+    {
+    $_[0]->[REFERENCES] = undef;
+    };
+
+
+###############################################################################
+# Group: Information Functions
+
+#
+#   Function: IsDefined
+#
+#   Returns whether the symbol is defined anywhere or not.  If it's not, that means it's just a potential interpretation of a
+#   reference.
+#
+sub IsDefined
+    {
+    return defined $_[0]->[GLOBAL_DEFINITION];
+    };
+
+#
+#   Function: IsDefinedIn
+#
+#   Returns whether the symbol is defined in the passed <FileName>.
+#
+sub IsDefinedIn #(file)
+    {
+    my ($self, $file) = @_;
+    return ($self->IsDefined() && exists $self->[DEFINITIONS]{$file});
+    };
+
+
+#
+#   Function: Definitions
+#
+#   Returns an array of all the <FileNames> that define this symbol.  If none do, will return an empty array.
+#
+sub Definitions
+    {
+    my $self = shift;
+
+    if ($self->IsDefined())
+        {  return keys %{$self->[DEFINITIONS]};  }
+    else
+        {  return ( );  };
+    };
+
+
+#
+#   Function: GlobalDefinition
+#
+#   Returns the <FileName> that contains the global definition of this symbol, or undef if the symbol isn't defined.
+#
+sub GlobalDefinition
+    {
+    return $_[0]->[GLOBAL_DEFINITION];
+    };
+
+
+#
+#   Function: TypeDefinedIn
+#
+#   Returns the <TopicType> of the symbol defined in the passed <FileName>, or undef if it's not defined in that file.
+#
+sub TypeDefinedIn #(file)
+    {
+    my ($self, $file) = @_;
+
+    if ($self->IsDefined())
+        {  return $self->[DEFINITIONS]{$file}->Type();  }
+    else
+        {  return undef;  };
+    };
+
+
+#
+#   Function: GlobalType
+#
+#   Returns the <TopicType> of the global definition, or undef if the symbol isn't defined.
+#
+sub GlobalType
+    {
+    my $self = shift;
+
+    my $globalDefinition = $self->GlobalDefinition();
+
+    if (!defined $globalDefinition)
+        {  return undef;  }
+    else
+        {  return $self->[DEFINITIONS]{$globalDefinition}->Type();  };
+    };
+
+
+#
+#   Function: PrototypeDefinedIn
+#
+#   Returns the prototype of symbol defined in the passed <FileName>, or undef if it doesn't exist or is not defined in that file.
+#
+sub PrototypeDefinedIn #(file)
+    {
+    my ($self, $file) = @_;
+
+    if ($self->IsDefined())
+        {  return $self->[DEFINITIONS]{$file}->Prototype();  }
+    else
+        {  return undef;  };
+    };
+
+
+#
+#   Function: GlobalPrototype
+#
+#   Returns the prototype of the global definition.  Will be undef if it doesn't exist or the symbol isn't defined.
+#
+sub GlobalPrototype
+    {
+    my $self = shift;
+
+    my $globalDefinition = $self->GlobalDefinition();
+
+    if (!defined $globalDefinition)
+        {  return undef;  }
+    else
+        {  return $self->[DEFINITIONS]{$globalDefinition}->Prototype();  };
+    };
+
+
+#
+#   Function: SummaryDefinedIn
+#
+#   Returns the summary of symbol defined in the passed <FileName>, or undef if it doesn't exist or is not defined in that file.
+#
+sub SummaryDefinedIn #(file)
+    {
+    my ($self, $file) = @_;
+
+    if ($self->IsDefined())
+        {  return $self->[DEFINITIONS]{$file}->Summary();  }
+    else
+        {  return undef;  };
+    };
+
+
+#
+#   Function: GlobalSummary
+#
+#   Returns the summary of the global definition.  Will be undef if it doesn't exist or the symbol isn't defined.
+#
+sub GlobalSummary
+    {
+    my $self = shift;
+
+    my $globalDefinition = $self->GlobalDefinition();
+
+    if (!defined $globalDefinition)
+        {  return undef;  }
+    else
+        {  return $self->[DEFINITIONS]{$globalDefinition}->Summary();  };
+    };
+
+
+#
+#   Function: HasReferences
+#
+#   Returns whether the symbol can be interpreted as any references.
+#
+sub HasReferences
+    {
+    return defined $_[0]->[REFERENCES];
+    };
+
+#
+#   Function: References
+#
+#   Returns an array of all the reference strings that can be interpreted as this symbol.  If none, will return an empty array.
+#
+sub References
+    {
+    if (defined $_[0]->[REFERENCES])
+        {  return keys %{$_[0]->[REFERENCES]};  }
+    else
+        {  return ( );  };
+    };
+
+
+#
+#   Function: ReferencesAndScores
+#
+#   Returns a hash of all the references that can be interpreted as this symbol and their scores.  The keys are the reference
+#   strings, and the values are the scores.  If none, will return an empty hash.
+#
+sub ReferencesAndScores
+    {
+    if (defined $_[0]->[REFERENCES])
+        {  return %{$_[0]->[REFERENCES]};  }
+    else
+        {  return ( );  };
+    };
+
+1;

Added: trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/SymbolTable/SymbolDefinition.pm
===================================================================
--- trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/SymbolTable/SymbolDefinition.pm	                        (rev 0)
+++ trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/SymbolTable/SymbolDefinition.pm	2009-07-01 15:30:00 UTC (rev 4275)
@@ -0,0 +1,96 @@
+###############################################################################
+#
+#   Package: NaturalDocs::SymbolTable::SymbolDefinition
+#
+###############################################################################
+#
+#   A class representing a symbol definition.  This does not store the definition symbol, class, or file.
+#
+###############################################################################
+
+# 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::SymbolTable::SymbolDefinition;
+
+
+###############################################################################
+# Group: Implementation
+
+#
+#   Constants: Members
+#
+#   The class is implemented as a blessed arrayref.  The following constants are its members.
+#
+#       TYPE  - The symbol <TopicType>.
+#       PROTOTYPE  - The symbol's prototype, if applicable.  Will be undef otherwise.
+#       SUMMARY - The symbol's summary, if applicable.  Will be undef otherwise.
+#
+use constant TYPE => 0;
+use constant PROTOTYPE => 1;
+use constant SUMMARY => 2;
+# New depends on the order of the constants.
+
+
+###############################################################################
+# Group: Functions
+
+#
+#   Function: New
+#
+#   Creates and returns a new object.
+#
+#   Parameters:
+#
+#       type - The symbol <TopicType>.
+#       prototype  - The symbol prototype, if applicable.  Undef otherwise.
+#       summary - The symbol's summary, if applicable.  Undef otherwise.
+#
+sub New #(type, prototype, summary)
+    {
+    # This depends on the parameter list being the same as the constant order.
+
+    my $package = shift;
+
+    my $object = [ @_ ];
+    bless $object, $package;
+
+    return $object;
+    };
+
+
+#   Function: Type
+#   Returns the definition's <TopicType>.
+sub Type
+    {  return $_[0]->[TYPE];  };
+
+# Function: SetType
+# Changes the <TopicType>.
+sub SetType #(type)
+    {  $_[0]->[TYPE] = $_[1];  };
+
+#   Function: Prototype
+#   Returns the definition's prototype, or undef if it doesn't have one.
+sub Prototype
+    {  return $_[0]->[PROTOTYPE];  };
+
+# Function: SetPrototype
+# Changes the prototype.
+sub SetPrototype #(prototype)
+    {  $_[0]->[PROTOTYPE] = $_[1];  };
+
+#   Function: Summary
+#   Returns the definition's summary, or undef if it doesn't have one.
+sub Summary
+    {  return $_[0]->[SUMMARY];  };
+
+# Function: SetSummary
+# Changes the summary.
+sub SetSummary #(summary)
+    {  $_[0]->[SUMMARY] = $_[1];  };
+
+
+1;

Added: trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/SymbolTable.pm
===================================================================
--- trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/SymbolTable.pm	                        (rev 0)
+++ trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/SymbolTable.pm	2009-07-01 15:30:00 UTC (rev 4275)
@@ -0,0 +1,1984 @@
+###############################################################################
+#
+#   Package: NaturalDocs::SymbolTable
+#
+###############################################################################
+#
+#   A package that handles all the gory details of managing symbols.  It handles where they are defined, which files
+#   reference them, if any are undefined or duplicated, and loading and saving them to a file.
+#
+#   Usage and Dependencies:
+#
+#       - At any time, <RebuildAllIndexes()> can be called.
+#
+#       - <NaturalDocs::Settings>, <NaturalDocs::Languages>, and <NaturalDocs::Project> must be initialized before use.
+#
+#       - <Load()> must be called to initialize the package.  At this point, the <Information Functions> will return the symbol
+#         table as of the last time Natural Docs was run.
+#
+#       - Note that <Load()> and <Save()> only manage <REFERENCE_TEXT> references.  All other reference types must be
+#         managed by their respective classes.  They should be readded after <Load()> to recreate the state of the last time
+#         Natural Docs was run.
+#
+#       - <Purge()> must be called, and then <NaturalDocs::Parser->ParseForInformation()> on all files that have changed so it
+#         can fully resolve the symbol table via the <Modification Functions>.  Afterwards <PurgeResolvingInfo()> can be called
+#         to reclaim some memory, and the symbol table will reflect the current state of the code.
+#
+#       - <Save()> must be called to commit any changes to the symbol table back to disk.
+#
+###############################################################################
+
+# This file is part of Natural Docs, which is Copyright (C) 2003-2008 Greg Valure
+# Natural Docs is licensed under the GPL
+
+
+use NaturalDocs::SymbolTable::Symbol;
+use NaturalDocs::SymbolTable::SymbolDefinition;
+use NaturalDocs::SymbolTable::Reference;
+use NaturalDocs::SymbolTable::File;
+use NaturalDocs::SymbolTable::ReferenceTarget;
+use NaturalDocs::SymbolTable::IndexElement;
+
+use strict;
+use integer;
+
+package NaturalDocs::SymbolTable;
+
+
+
+###############################################################################
+# Group: Variables
+
+#
+#   handle: SYMBOLTABLE_FILEHANDLE
+#
+#   The file handle used with <SymbolTable.nd>.
+#
+
+#
+#   hash: symbols
+#
+#   A hash of all <SymbolStrings>.  The keys are the <SymbolStrings> and the values are <NaturalDocs::SymbolTable::Symbol>
+#   objects.
+#
+#   Prior to <PurgeResolvingInfo()>, both defined symbols and symbols that are merely potential interpretations of references
+#   will be here.  Afterwards, only defined symbols will be here.
+#
+my %symbols;
+
+#
+#   hash: references
+#
+#   A hash of all references in the project.  The keys are <ReferenceStrings> and the values are
+#   <NaturalDocs::SymbolTable::Reference> objects.
+#
+#   Prior to <PurgeResolvingInfo()>, all possible interpretations will be stored for each reference.  Afterwards, only the current
+#   interpretation will be.
+#
+my %references;
+
+#
+#   hash: files
+#
+#   A hash of all the files that define symbols and references in the project.  The keys are the <FileNames>, and the values are
+#   <NaturalDocs::SymbolTable::File> objects.
+#
+#   After <PurgeResolvingInfo()>, this hash will be empty.
+#
+my %files;
+
+#
+#   object: watchedFile
+#
+#   A <NaturalDocs::SymbolTable::File> object of the file being watched for changes.  This is compared to the version in <files>
+#   to see if anything was changed since the last parse.
+#
+my $watchedFile;
+
+#
+#   string: watchedFileName
+#
+#   The <FileName> of the watched file, if any.  If there is no watched file, this will be undef.
+#
+my $watchedFileName;
+
+#
+#   hash: watchedFileSymbolDefinitions
+#
+#   A hashref of the symbol definition information for all the <SymbolStrings> in the watched file.  The keys are the symbol strings,
+#   and the values are <NaturalDocs::SymbolTable::SymbolDefinition> objects.
+#
+my %watchedFileSymbolDefinitions;
+
+
+#
+#   hash: indexes
+#
+#   A hash of generated symbol indexes.  The keys are <TopicTypes> and the values are sorted arrayrefs of
+#   <NaturalDocs::SymbolTable::IndexElements>, or undef if its empty.
+#
+my %indexes;
+
+
+#
+#   hash: indexChanges
+#
+#   A hash of all the indexes that have changed.  The keys are the <TopicTypes> and the entries are undef if they have not
+#   changed, or 1 if they have.  The key will not exist if the <TopicType> has not been checked.
+#
+my %indexChanges;
+
+
+#
+#   hash: indexSectionsWithContent
+#
+#   A hash of which sections in an index have content.  The keys are the <TopicTypes> of each index, and the values are
+#   arrayrefs of bools where the first represents symbols, the second numbers, and the rest A-Z.  If there is no information
+#   available for an index, it's entry will not exist here.
+#
+my %indexSectionsWithContent;
+
+
+#
+#   bool: rebuildIndexes
+#
+#   Whether all indexes should be rebuilt regardless of whether they have been changed.
+#
+my $rebuildIndexes;
+
+
+
+###############################################################################
+# Group: Files
+
+
+#
+#   File: SymbolTable.nd
+#
+#   The storage file for the symbol table.
+#
+#   Format:
+#
+#       > [BINARY_FORMAT]
+#       > [VersionInt: app version]
+#
+#       The file starts with the standard <BINARY_FORMAT> <VersionInt> header.
+#
+#       The first stage of the file is for symbol definitions, analogous to <symbols>.
+#
+#       > [SymbolString: symbol or undef to end] ...
+#       >
+#       > [UInt16: number of definitions]
+#       >
+#       >    [AString16: global definition file] [AString16: TopicType]
+#       >       [AString16: prototype] [AString16: summary]
+#       >
+#       >    [AString16: definition file] ...
+#       >
+#       >    ...
+#
+#       These blocks continue until the <SymbolString> is undef.  Only defined symbols will be included in this file, so
+#       number of definitions will never be zero.  The first one is always the global definition.  If a symbol does not have a
+#       prototype or summary, the UInt16 length of the string will be zero.
+#
+#       The second stage is for references, which is analogous to <references>.  Only <REFERENCE_TEXT> references are
+#       stored in this file, and their <Resolving Flags> are implied so they aren't stored either.
+#
+#       > [ReferenceString (no type, resolving flags): reference or undef to end]
+#       >
+#       > [UInt8: number of definition files]
+#       >    [AString16: definition file] [AString16: definition file] ...
+#
+#       These blocks continue until the <ReferenceString> is undef.  Since there can be multiple using <SymbolStrings>, those
+#       continue until the number of identifiers is zero.  Note that all interpretations are rebuilt rather than stored.
+#
+#   See Also:
+#
+#       <File Format Conventions>
+#
+#   Revisions:
+#
+#       1.3:
+#
+#           - Symbol <TopicTypes> were changed from UInt8s to AString16s, now that <TopicTypes> are strings instead of
+#             integer constants.
+#
+#       1.22:
+#
+#           - File format was completely rebuilt to accommodate the new symbol format and to be in binary.  To see the plain text
+#             format prior to 1.22, check out 1.21's version of this file from CVS.  It is too big a change to note here.
+#
+
+
+#
+#   File: IndexInfo.nd
+#
+#   The storage file for information about the indexes.
+#
+#   Format:
+#
+#       > [Standard Header]
+#
+#       The standard binary file header.
+#
+#       > [AString16: index topic name]
+#       > [uint8: symbols have content (0 or 1)]
+#       > [uint8: numbers have content (0 or 1)]
+#       > [uint8: A has content] [uint8: B has content] ...
+#       > ...
+#
+#       Every index that has information about it is stored with the topic type name first, then 28 uint8s that say whether that
+#       part of the index has content or not.  The first is for symbols, the second is for numbers, and the rest are for A-Z.  If an
+#       index's state is unknown, it won't appear in this file.
+#
+#   Revisions:
+#
+#       1.4:
+#
+#           - The file is introduced.
+#
+
+
+
+###############################################################################
+# Group: File Functions
+
+
+#
+#   Function: Load
+#
+#   Loads all data files from disk.
+#
+sub Load
+    {
+    my ($self) = @_;
+
+    $self->LoadSymbolTable();
+    $self->LoadIndexInfo();
+    };
+
+
+#
+#   Function: LoadSymbolTable
+#
+#   Loads <SymbolTable.nd> from disk.
+#
+sub LoadSymbolTable
+    {
+    my ($self) = @_;
+
+    my $fileIsOkay;
+
+    if (!NaturalDocs::Settings->RebuildData() &&
+        open(SYMBOLTABLE_FILEHANDLE, '<' . NaturalDocs::Project->DataFile('SymbolTable.nd')) )
+        {
+        # See if it's binary.
+        binmode(SYMBOLTABLE_FILEHANDLE);
+
+        my $firstChar;
+        read(SYMBOLTABLE_FILEHANDLE, $firstChar, 1);
+
+        if ($firstChar == ::BINARY_FORMAT())
+            {
+            my $version = NaturalDocs::Version->FromBinaryFile(\*SYMBOLTABLE_FILEHANDLE);
+
+            # 1.3 is incompatible with previous versions.
+
+            if (NaturalDocs::Version->CheckFileFormat( $version, NaturalDocs::Version->FromString('1.3') ))
+                {  $fileIsOkay = 1;  }
+            else
+                {  close(SYMBOLTABLE_FILEHANDLE);  };
+            }
+
+        else
+            {  close(SYMBOLTABLE_FILEHANDLE);  };
+        };
+
+
+    if (!$fileIsOkay)
+        {
+        NaturalDocs::Project->ReparseEverything();
+        return;
+        }
+
+    my $raw;
+
+
+    # Symbols
+
+    for (;;)
+        {
+        # [SymbolString: symbol or undef to end]
+
+        my $symbol = NaturalDocs::SymbolString->FromBinaryFile(\*SYMBOLTABLE_FILEHANDLE);
+
+        if (!defined $symbol)
+            {  last;  };
+
+        my $symbolObject = NaturalDocs::SymbolTable::Symbol->New();
+        $symbols{$symbol} = $symbolObject;
+
+        # [UInt16: number of definitions]
+
+        read(SYMBOLTABLE_FILEHANDLE, $raw, 2);
+        my $definitionCount = unpack('n', $raw);
+
+        do
+            {
+            # [AString16: (global?) definition file]
+
+            read(SYMBOLTABLE_FILEHANDLE, $raw, 2);
+            my $fileLength = unpack('n', $raw);
+
+            my $file;
+            read(SYMBOLTABLE_FILEHANDLE, $file, $fileLength);
+
+            # [AString16: TopicType]
+
+            read(SYMBOLTABLE_FILEHANDLE, $raw, 2);
+            my $typeLength = unpack('n', $raw);
+
+            my $type;
+            read(SYMBOLTABLE_FILEHANDLE, $type, $typeLength);
+
+            # [AString16: prototype]
+
+            read(SYMBOLTABLE_FILEHANDLE, $raw, 2);
+            my $prototypeLength = unpack('n', $raw);
+
+            my $prototype;
+            if ($prototypeLength)
+                {  read(SYMBOLTABLE_FILEHANDLE, $prototype, $prototypeLength);  };
+
+            # [AString16: summary]
+
+            read(SYMBOLTABLE_FILEHANDLE, $raw, 2);
+            my $summaryLength = unpack('n', $raw);
+
+            my $summary;
+            if ($summaryLength)
+                {  read(SYMBOLTABLE_FILEHANDLE, $summary, $summaryLength);  };
+
+            $symbolObject->AddDefinition($file, $type, $prototype, $summary);
+
+            # Add it.
+
+            if (!exists $files{$file})
+                {  $files{$file} = NaturalDocs::SymbolTable::File->New();  };
+
+            $files{$file}->AddSymbol($symbol);
+
+            $definitionCount--;
+            }
+        while ($definitionCount);
+        };
+
+
+    # References
+
+    for (;;)
+        {
+        # [ReferenceString (no type, resolving flags): reference or undef to end]
+
+        my $referenceString = NaturalDocs::ReferenceString->FromBinaryFile(\*SYMBOLTABLE_FILEHANDLE,
+                                                                                                              ::BINARYREF_NOTYPE() |
+                                                                                                              ::BINARYREF_NORESOLVINGFLAGS(),
+                                                                                                              ::REFERENCE_TEXT(), undef);
+
+        if (!defined $referenceString)
+            {  last;  };
+
+        my $referenceObject = NaturalDocs::SymbolTable::Reference->New();
+        $references{$referenceString} = $referenceObject;
+
+        # [UInt8: number of definition files]
+
+        read(SYMBOLTABLE_FILEHANDLE, $raw, 1);
+        my $definitionCount = unpack('C', $raw);
+        do
+            {
+            # [AString16: definition file] [AString16: definition file] ...
+
+            read(SYMBOLTABLE_FILEHANDLE, $raw, 2);
+            my $definitionLength = unpack('n', $raw);
+
+            my $definition;
+            read(SYMBOLTABLE_FILEHANDLE, $definition, $definitionLength);
+
+            # Add it.
+
+            $referenceObject->AddDefinition($definition);
+
+            if (!exists $files{$definition})
+                {  $files{$definition} = NaturalDocs::SymbolTable::File->New();  };
+
+            $files{$definition}->AddReference($referenceString);
+
+            $definitionCount--;
+            }
+        while ($definitionCount);
+
+        $self->GenerateInterpretations($referenceString);
+        $self->InterpretReference($referenceString);
+        };
+
+    close(SYMBOLTABLE_FILEHANDLE);
+    };
+
+
+#
+#   Function: LoadIndexInfo
+#
+#   Loads <IndexInfo.nd> from disk.
+#
+sub LoadIndexInfo
+    {
+    my ($self) = @_;
+
+    if (NaturalDocs::Settings->RebuildData())
+        {  return;  };
+
+    my $version = NaturalDocs::BinaryFile->OpenForReading( NaturalDocs::Project->DataFile('IndexInfo.nd') );
+
+    if (!defined $version)
+        {  return;  }
+
+    # The file format hasn't changed since it was introduced.
+    if (!NaturalDocs::Version->CheckFileFormat($version))
+        {
+        NaturalDocs::BinaryFile->Close();
+        return;
+        };
+
+    my $topicTypeName;
+    while ($topicTypeName = NaturalDocs::BinaryFile->GetAString16())
+        {
+        my $topicType = NaturalDocs::Topics->TypeFromName($topicTypeName);
+        my $content = [ ];
+
+        for (my $i = 0; $i < 28; $i++)
+            {  push @$content, NaturalDocs::BinaryFile->GetUInt8();  };
+
+        if (defined $topicType)  # The name in the file could be from a type that was deleted
+            {  $indexSectionsWithContent{$topicType} = $content;  };
+        };
+
+    NaturalDocs::BinaryFile->Close();
+    };
+
+
+#
+#   Function: Purge
+#
+#   Purges the symbol table of all symbols and references from files that no longer have Natural Docs content.
+#
+sub Purge
+    {
+    my ($self) = @_;
+
+    my $filesToPurge = NaturalDocs::Project->FilesToPurge();
+
+    # We do this in two stages.  First we delete all the references, and then we delete all the definitions.  This causes us to go
+    # through the list twice, but it makes sure no purged files get added to the build list.  For example, if we deleted all of
+    # Purge File A's references and definitions, and Purge File B had a reference to one of those symbols, Purge File B
+    # would be added to the build list because one of its references changed.  By removing all the references in all the files
+    # before removing the definitions, we avoid this.
+
+    foreach my $file (keys %$filesToPurge)
+        {
+        if (exists $files{$file})
+            {
+            my @references = $files{$file}->References();
+            foreach my $reference (@references)
+                {  $self->DeleteReference($reference, $file);  };
+            };
+        };
+
+    foreach my $file (keys %$filesToPurge)
+        {
+        if (exists $files{$file})
+            {
+            my @symbols = $files{$file}->Symbols();
+            foreach my $symbol (@symbols)
+                {  $self->DeleteSymbol($symbol, $file);  };
+
+            delete $files{$file};
+            };
+        };
+    };
+
+
+#
+#   Function: Save
+#
+#   Saves all data files to disk.
+#
+sub Save
+    {
+    my ($self) = @_;
+
+    $self->SaveSymbolTable();
+    $self->SaveIndexInfo();
+    };
+
+
+#
+#   Function: SaveSymbolTable
+#
+#   Saves <SymbolTable.nd> to disk.
+#
+sub SaveSymbolTable
+    {
+    my ($self) = @_;
+
+    open (SYMBOLTABLE_FILEHANDLE, '>' . NaturalDocs::Project->DataFile('SymbolTable.nd'))
+        or die "Couldn't save " . NaturalDocs::Project->DataFile('SymbolTable.nd') . ".\n";
+
+    binmode(SYMBOLTABLE_FILEHANDLE);
+
+    print SYMBOLTABLE_FILEHANDLE '' . ::BINARY_FORMAT();
+
+    NaturalDocs::Version->ToBinaryFile(\*SYMBOLTABLE_FILEHANDLE, NaturalDocs::Settings->AppVersion());
+
+
+    # Symbols
+
+    while (my ($symbol, $symbolObject) = each %symbols)
+        {
+        # Only existing symbols.
+        if ($symbolObject->IsDefined())
+            {
+            # [SymbolString: symbol or undef to end]
+
+            NaturalDocs::SymbolString->ToBinaryFile(\*SYMBOLTABLE_FILEHANDLE, $symbol);
+
+            # [UInt16: number of definitions]
+
+            my @definitions = $symbolObject->Definitions();
+            print SYMBOLTABLE_FILEHANDLE pack('n', scalar @definitions);
+
+            # [AString16: global definition file] [AString16: TopicType]
+
+            print SYMBOLTABLE_FILEHANDLE pack('nA*nA*', length $symbolObject->GlobalDefinition(),
+                                                                                   $symbolObject->GlobalDefinition(),
+                                                                                   length $symbolObject->GlobalType(),
+                                                                                   $symbolObject->GlobalType());
+
+            # [AString16: prototype]
+
+            my $prototype = $symbolObject->GlobalPrototype();
+
+            if (defined $prototype)
+                {  print SYMBOLTABLE_FILEHANDLE pack('nA*', length($prototype), $prototype);  }
+            else
+                {  print SYMBOLTABLE_FILEHANDLE pack('n', 0);  };
+
+            # [AString16: summary]
+
+            my $summary = $symbolObject->GlobalSummary();
+
+            if (defined $summary)
+                {  print SYMBOLTABLE_FILEHANDLE pack('nA*', length($summary), $summary);  }
+            else
+                {  print SYMBOLTABLE_FILEHANDLE pack('n', 0);  };
+
+
+            foreach my $definition (@definitions)
+                {
+                if ($definition ne $symbolObject->GlobalDefinition())
+                    {
+                    # [AString16: definition file] [AString16: TopicType]
+
+                    print SYMBOLTABLE_FILEHANDLE pack('nA*nA*', length $definition, $definition,
+                                                                                           length $symbolObject->TypeDefinedIn($definition),
+                                                                                           $symbolObject->TypeDefinedIn($definition));
+
+                    # [AString16: prototype]
+
+                    my $prototype = $symbolObject->PrototypeDefinedIn($definition);
+
+                    if (defined $prototype)
+                        {  print SYMBOLTABLE_FILEHANDLE pack('nA*', length($prototype), $prototype);  }
+                    else
+                        {  print SYMBOLTABLE_FILEHANDLE pack('n', 0);  };
+
+                    # [AString16: summary]
+
+                    my $summary = $symbolObject->SummaryDefinedIn($definition);
+
+                    if (defined $summary)
+                        {  print SYMBOLTABLE_FILEHANDLE pack('nA*', length($summary), $summary);  }
+                    else
+                        {  print SYMBOLTABLE_FILEHANDLE pack('n', 0);  };
+                    };
+                };
+            };
+        };
+
+     # [SymbolString: symbol or undef to end]
+
+     NaturalDocs::SymbolString->ToBinaryFile(\*SYMBOLTABLE_FILEHANDLE, undef);
+
+
+     # References
+
+    while (my ($reference, $referenceObject) = each %references)
+        {
+        my $type = NaturalDocs::ReferenceString->TypeOf($reference);
+
+        if ($type == ::REFERENCE_TEXT())
+            {
+            # [ReferenceString (no type, resolving flags): reference or undef to end]
+
+            NaturalDocs::ReferenceString->ToBinaryFile(\*SYMBOLTABLE_FILEHANDLE, $reference,
+                                                                             ::BINARYREF_NOTYPE() | ::BINARYREF_NORESOLVINGFLAGS());
+
+            # [UInt8: number of definition files]
+
+            my @definitions = $referenceObject->Definitions();
+            print SYMBOLTABLE_FILEHANDLE pack('C', scalar @definitions);
+
+            # [AString16: definition file] [AString16: definition file] ...
+
+            foreach my $definition (@definitions)
+                {
+                print SYMBOLTABLE_FILEHANDLE pack('nA*', length($definition), $definition);
+                };
+            };
+        };
+
+    # [ReferenceString (no type, resolving flags): reference or undef to end]
+
+    NaturalDocs::ReferenceString->ToBinaryFile(\*SYMBOLTABLE_FILEHANDLE, undef,
+                                                                     ::BINARYREF_NOTYPE() | ::BINARYREF_NORESOLVINGFLAGS());
+
+    close(SYMBOLTABLE_FILEHANDLE);
+    };
+
+
+#
+#   Function: SaveIndexInfo
+#
+#   Saves <IndexInfo.nd> to disk.
+#
+sub SaveIndexInfo
+    {
+    my ($self) = @_;
+
+    NaturalDocs::BinaryFile->OpenForWriting( NaturalDocs::Project->DataFile('IndexInfo.nd') );
+
+    while (my ($topicType, $content) = each %indexSectionsWithContent)
+        {
+        NaturalDocs::BinaryFile->WriteAString16( NaturalDocs::Topics->NameOfType($topicType) );
+
+        for (my $i = 0; $i < 28; $i++)
+            {
+            if ($content->[$i])
+                {  NaturalDocs::BinaryFile->WriteUInt8(1);  }
+            else
+                {  NaturalDocs::BinaryFile->WriteUInt8(0);  };
+            };
+        };
+
+    NaturalDocs::BinaryFile->Close();
+    };
+
+
+
+###############################################################################
+# Group: Modification Functions
+# These functions should not be called after <PurgeResolvingInfo()>.
+
+#
+#   Function: AddSymbol
+#
+#   Adds a symbol definition to the table, if it doesn't already exist.  If the definition changes or otherwise requires the files that
+#   reference it to be updated, the function will call <NaturalDocs::Project->RebuildFile()> to make sure that they are.
+#
+#   Parameters:
+#
+#       symbol  - The <SymbolString>.
+#       file        - The <FileName> where it's defined.
+#       type      - The symbol's <TopicType>.
+#       prototype - The symbol's prototype, if applicable.
+#       summary - The symbol's summary, if applicable.
+#
+sub AddSymbol #(symbol, file, type, prototype, summary)
+    {
+    my ($self, $symbol, $file, $type, $prototype, $summary) = @_;
+
+
+    # If the symbol doesn't exist...
+    if (!exists $symbols{$symbol})
+        {
+        # Create the symbol.  There are no references that could be interpreted as this or else it would have existed already.
+
+        my $newSymbol = NaturalDocs::SymbolTable::Symbol->New();
+        $newSymbol->AddDefinition($file, $type, $prototype, $summary);
+
+        $symbols{$symbol} = $newSymbol;
+
+        $self->OnIndexChange($type);
+        NaturalDocs::Project->RebuildFile($file);
+        }
+
+
+    # If the symbol already exists...
+    else
+        {
+        my $symbolObject = $symbols{$symbol};
+
+        # If the symbol isn't defined, i.e. it was a potential interpretation only...
+        if (!$symbolObject->IsDefined())
+            {
+            $symbolObject->AddDefinition($file, $type, $prototype, $summary);
+
+            # See if this symbol provides a better interpretation of any references.  We can assume this symbol has interpretations
+            # because the object won't exist without either that or definitions.
+
+            my %referencesAndScores = $symbolObject->ReferencesAndScores();
+
+            while (my ($referenceString, $referenceScore) = each %referencesAndScores)
+                {
+                my $referenceObject = $references{$referenceString};
+
+                if (!$referenceObject->HasCurrentInterpretation() ||
+                    $referenceScore > $referenceObject->CurrentScore())
+                    {
+                    $referenceObject->SetCurrentInterpretation($symbol);
+                    $self->OnInterpretationChange($referenceString);
+                    };
+                };
+
+            $self->OnIndexChange($type);
+            NaturalDocs::Project->RebuildFile($file);
+            }
+
+        # If the symbol is defined but not in this file...
+        elsif (!$symbolObject->IsDefinedIn($file))
+            {
+            $symbolObject->AddDefinition($file, $type, $prototype, $summary);
+
+            $self->OnIndexChange($type);
+            NaturalDocs::Project->RebuildFile($file);
+
+            # We don't have to check other files because if the symbol is defined it already has a global definiton,
+            # and everything else is either using that or its own definition, and thus wouldn't be affected by this.
+            };
+
+        # If the symbol was already defined in this file, ignore it.
+
+        };
+
+
+    # Add it to the file index.
+
+    if (!exists $files{$file})
+        {  $files{$file} = NaturalDocs::SymbolTable::File->New();  };
+
+    $files{$file}->AddSymbol($symbol);
+
+
+    # Add it to the watched file, if necessary.
+
+    if (defined $watchedFileName)
+        {
+        $watchedFile->AddSymbol($symbol);
+
+        if (!exists $watchedFileSymbolDefinitions{$symbol})
+            {
+            $watchedFileSymbolDefinitions{$symbol} =
+                 NaturalDocs::SymbolTable::SymbolDefinition->New($type, $prototype, $summary);
+            };
+        };
+    };
+
+
+#
+#   Function: AddReference
+#
+#   Adds a reference to the table, if it doesn't already exist.
+#
+#   Parameters:
+#
+#       type        - The <ReferenceType>.
+#       symbol    - The reference <SymbolString>.
+#       scope      - The scope <SymbolString> it appears in.
+#       using       - An arrayref of scope <SymbolStrings> accessible to the reference via "using" statements, or undef if none.
+#       file          - The <FileName> where the reference appears.  This is not required unless the type is <REFERENCE_TEXT>.
+#       resolvingFlags - The <Resolving Flags> of the reference.  They will be ignored if the type is <REFERENCE_TEXT>.
+#
+#   Alternate Parameters:
+#
+#       referenceString - The <ReferenceString> to add.
+#       file - The <FileName> where the reference appears.  This is not required unless the type is <REFERENCE_TEXT>.
+#
+sub AddReference #(type, symbol, scope, using, file, resolvingFlags) or (referenceString, file)
+    {
+    my ($self, $referenceString, $file);
+
+    if (scalar @_ <= 3)
+        {
+        ($self, $referenceString, $file) = @_;
+        }
+    else
+        {
+        my ($type, $symbol, $scope, $using, $resolvingFlags);
+        ($self, $type, $symbol, $scope, $using, $file, $resolvingFlags) = @_;
+
+        $referenceString = NaturalDocs::ReferenceString->MakeFrom($type, $symbol,
+                                                                                                   NaturalDocs::Languages->LanguageOf($file)->Name(),
+                                                                                                   $scope, $using, $resolvingFlags);
+        };
+
+
+    # If the reference doesn't exist...
+    if (!exists $references{$referenceString})
+        {
+        my $referenceObject = NaturalDocs::SymbolTable::Reference->New();
+
+        $references{$referenceString} = $referenceObject;
+
+        $self->GenerateInterpretations($referenceString);
+        $self->InterpretReference($referenceString);
+        }
+
+
+    if (defined $file)
+        {
+        $references{$referenceString}->AddDefinition($file);
+
+
+        # Add it to the file index.
+
+        if (!exists $files{$file})
+            {  $files{$file} = NaturalDocs::SymbolTable::File->New();  };
+
+        $files{$file}->AddReference($referenceString);
+
+
+        # Add it to the watched file, if necessary.
+
+        if (defined $watchedFileName)
+            {  $watchedFile->AddReference($referenceString);  };
+        };
+    };
+
+
+#
+#   Function: WatchFileForChanges
+#
+#   Tracks a file to see if any symbols or references were changed or deleted in ways that would require other files to be rebuilt.
+#   Assumes that after this function call, the entire file will be parsed again, and thus every symbol and reference will go through
+#   <AddSymbol()> and <AddReference()>.  Afterwards, call <AnalyzeChanges()> to handle any differences.
+#
+#   Parameters:
+#
+#       file - The <FileName> to watch.
+#
+sub WatchFileForChanges #(file)
+    {
+    my ($self, $file) = @_;
+
+    $watchedFile = NaturalDocs::SymbolTable::File->New();
+    $watchedFileName = $file;
+    %watchedFileSymbolDefinitions = ( );
+    };
+
+
+#
+#   Function: AnalyzeChanges
+#
+#   Handles any changes found when reparsing a file using <WatchFileForChanges()>.
+#
+sub AnalyzeChanges
+    {
+    my ($self) = @_;
+
+    if (exists $files{$watchedFileName})
+        {
+
+        # Go through the references and remove any that were deleted.  Ones that were added will have already been added to
+        # the table in AddReference().
+
+        my @references = $files{$watchedFileName}->References();
+        foreach my $reference (@references)
+            {
+            if (!$watchedFile->DefinesReference($reference))
+                {  $self->DeleteReference($reference, $watchedFileName);  };
+            };
+        };
+
+    # We have to check if the watched file exists again because DeleteReference() could have removed it.  I'm still not sure how a
+    # file could have references without symbols, but apparently it's happened in the real world because it's crashed on people.
+    if (exists $files{$watchedFileName})
+        {
+        # Go through the symbols.
+
+        my $rebuildFile;
+
+        my @symbols = $files{$watchedFileName}->Symbols();
+        foreach my $symbol (@symbols)
+            {
+            # Delete symbols that don't exist.
+
+            if (!$watchedFile->DefinesSymbol($symbol))
+                {
+                $self->DeleteSymbol($symbol, $watchedFileName);
+                $rebuildFile = 1;
+                }
+
+            else
+                {
+                my $symbolObject = $symbols{$symbol};
+                my $newSymbolDef = $watchedFileSymbolDefinitions{$symbol};
+
+                # Update symbols that changed.
+
+                if ( $symbolObject->TypeDefinedIn($watchedFileName) ne $newSymbolDef->Type() ||
+                     $symbolObject->PrototypeDefinedIn($watchedFileName) ne $newSymbolDef->Prototype() ||
+                     $symbolObject->SummaryDefinedIn($watchedFileName) ne $newSymbolDef->Summary() )
+                    {
+                    $self->OnIndexChange($symbolObject->TypeDefinedIn($watchedFileName));
+                    $self->OnIndexChange($newSymbolDef->Type());
+                    $rebuildFile = 1;
+
+                    $symbolObject->ChangeDefinition($watchedFileName, $newSymbolDef->Type(), $newSymbolDef->Prototype(),
+                                                                       $newSymbolDef->Summary());
+
+                    # If the symbol definition was the global one, we need to update all files that reference it.  If it wasn't, the only file
+                    # that could references it is itself, and the only way the symbol definition could change in the first place was if it was
+                    # itself changed.
+                    if ($symbolObject->GlobalDefinition() eq $watchedFileName)
+                        {
+                        # Rebuild the files that have references to this symbol
+                        my @references = $symbolObject->References();
+                        foreach my $reference (@references)
+                            {
+                            if ($references{$reference}->CurrentInterpretation() eq $symbol)
+                                {  $self->OnTargetSymbolChange($reference);  };
+                            }; # While references
+                        }; # If global definition is watched file
+                    }; # If the symbol definition changed
+                }; # If the symbol still exists
+            }; # foreach symbol in watched file
+
+        if ($rebuildFile)
+            {  NaturalDocs::Project->RebuildFile($watchedFileName);  };
+
+        };
+
+
+    $watchedFile = undef;
+    $watchedFileName = undef;
+    %watchedFileSymbolDefinitions = ( );
+    };
+
+
+#
+#   Function: DeleteReference
+#
+#   Deletes a reference from the table.
+#
+#   Be careful with this function, as deleting a reference means there are no more of them in the file at all.  The tables do not
+#   keep track of how many times references appear in a file.  In these cases you should instead call <WatchFileForChanges()>,
+#   reparse the file, thus readding all the references, and call <AnalyzeChanges()>.
+#
+#   <REFERENCE_TEXT> references should *always* be managed with <WatchFileForChanges()> and <AnalyzeChanges()>.
+#   This function should only be used externally for other types of references.
+#
+#   Parameters:
+#
+#       referenceString - The <ReferenceString>.
+#       file - The <FileName> where the reference is.  This is not required unless the type is <REFERENCE_TEXT>.
+#
+sub DeleteReference #(referenceString, file)
+    {
+    my ($self, $referenceString, $file) = @_;
+
+
+    # If the reference exists...
+    if (exists $references{$referenceString})
+        {
+        my $referenceObject = $references{$referenceString};
+
+        if (defined $file)
+            {  $referenceObject->DeleteDefinition($file);  };
+
+        # If there are no other definitions, or it doesn't use file definitions to begin with...
+        if (!$referenceObject->IsDefined())
+            {
+            my @interpretations = $referenceObject->Interpretations();
+            foreach my $interpretation (@interpretations)
+                {
+                $symbols{$interpretation}->DeleteReference($referenceString);
+                };
+
+            delete $references{$referenceString};
+            };
+
+
+        if (defined $file)
+            {
+            # Remove it from the file index.
+
+            $files{$file}->DeleteReference($referenceString);
+
+            if (!$files{$file}->HasAnything())
+                {  delete $files{$file};  };
+
+            # We don't need to worry about the watched file, since this function will only be called by AnalyzeChanges() and
+            # LoadAndPurge().
+            };
+        };
+    };
+
+
+#
+#   Function: RebuildAllIndexes
+#
+#   When called, it makes sure all indexes are listed as changed by <IndexChanged()>, regardless of whether they actually did
+#   or not.
+#
+#   This can be called at any time.
+#
+sub RebuildAllIndexes
+    {
+    my $self = shift;
+    $rebuildIndexes = 1;
+    };
+
+
+#
+#   Function: PurgeResolvingInfo
+#
+#   Purges unnecessary information from the symbol table after it is fully resolved.  This will reduce the memory footprint for the
+#   build stage.  After calling this function, you can only call the <Information Functions> and <Save()>.
+#
+sub PurgeResolvingInfo
+    {
+    my ($self) = @_;
+
+    # Go through the symbols.  We don't need to keep around potential symbols anymore, nor do we need what references can
+    # be interpreted as the defined ones.
+
+    while (my ($symbol, $symbolObject) = each %symbols)
+        {
+        if ($symbolObject->IsDefined())
+            {  $symbolObject->DeleteAllReferences();  }
+        else
+            {  delete $symbols{$symbol};  };
+        };
+
+
+    # Go through the references.  We don't need any of the interpretations except for the current.
+
+    foreach my $referenceObject (values %references)
+        {  $referenceObject->DeleteAllInterpretationsButCurrent();  };
+
+
+    # We don't need the information by file at all.
+
+    %files = ( );
+    };
+
+
+#
+#   Function: PurgeIndexes
+#
+#   Clears all generated indexes.
+#
+sub PurgeIndexes
+    {
+    my ($self) = @_;
+    %indexes = ( );
+    };
+
+
+###############################################################################
+# Group: Information Functions
+# These functions should not be called until the symbol table is fully resolved.
+
+
+#
+#   Function: References
+#
+#   Returns what the passed reference information resolve to, if anything.  Note that this only works if the reference had
+#   been previously added to the table via <AddReference()> with the exact same parameters.
+#
+#   Parameters:
+#
+#       type     - The <ReferenceType>.
+#       symbol - The reference <SymbolString>.
+#       scope   - The scope <SymbolString> the reference appears in, or undef if none.
+#       using    - An arrayref of scope <SymbolStrings> available to the reference via using statements.
+#       file       - The source <FileName> the reference appears in, or undef if none.
+#       resolvingFlags - The <Resolving Flags> of the reference.  Ignored if the type is <REFERENCE_TEXT>.
+#
+#   Alternate Parameters:
+#
+#       referenceString - The <ReferenceString> to resolve.
+#       file - The source <FileName> the reference appears in, or undef if none.
+#
+#   Returns:
+#
+#       A <NaturalDocs::SymbolTable::ReferenceTarget> object, or undef if the reference doesn't resolve to anything.
+#
+sub References #(type, symbol, scope, using, file, resolvingFlags) or (referenceString, file)
+    {
+    my ($self, $referenceString, $file);
+
+    if (scalar @_ <= 3)
+        {  ($self, $referenceString, $file) = @_;  }
+    else
+        {
+        my ($type, $symbol, $scope, $using, $resolvingFlags);
+        ($self, $type, $symbol, $scope, $using, $file, $resolvingFlags) = @_;
+
+        $referenceString = NaturalDocs::ReferenceString->MakeFrom($type, $symbol,
+                                                                                                  NaturalDocs::Languages->LanguageOf($file)->Name(),
+                                                                                                  $scope, $using, $resolvingFlags);
+        };
+
+    if (exists $references{$referenceString} && $references{$referenceString}->HasCurrentInterpretation())
+        {
+        my $targetSymbol = $references{$referenceString}->CurrentInterpretation();
+        my $targetObject = $symbols{$targetSymbol};
+
+        my $targetFile;
+        my $targetType;
+        my $targetPrototype;
+        my $targetSummary;
+
+        if (defined $file && $targetObject->IsDefinedIn($file))
+            {
+            $targetFile = $file;
+            $targetType = $targetObject->TypeDefinedIn($file);
+            $targetPrototype = $targetObject->PrototypeDefinedIn($file);
+            $targetSummary = $targetObject->SummaryDefinedIn($file);
+            }
+        else
+            {
+            $targetFile = $targetObject->GlobalDefinition();
+            $targetType = $targetObject->GlobalType();
+            $targetPrototype = $targetObject->GlobalPrototype();
+            $targetSummary = $targetObject->GlobalSummary();
+            };
+
+        return NaturalDocs::SymbolTable::ReferenceTarget->New($targetSymbol, $targetFile, $targetType, $targetPrototype,
+                                                                                             $targetSummary);
+        }
+
+    else
+        {  return undef;  };
+    };
+
+
+#
+#   Function: Lookup
+#
+#   Returns information on the passed <SymbolString>, if it exists.  Note that the symbol must be fully resolved.
+#
+#   Parameters:
+#
+#       symbol - The <SymbolString>.
+#       file - The source <FileName> the reference appears in, or undef if none.
+#
+#   Returns:
+#
+#       A <NaturalDocs::SymbolTable::ReferenceTarget> object, or undef if the symbol isn't defined.
+#
+sub Lookup #(symbol, file)
+    {
+    my ($self, $symbol, $file) = @_;
+
+    my $symbolObject = $symbols{$symbol};
+
+    if (defined $symbolObject)
+        {
+        my $targetFile;
+        my $targetType;
+        my $targetPrototype;
+        my $targetSummary;
+
+        if (defined $file && $symbolObject->IsDefinedIn($file))
+            {
+            $targetFile = $file;
+            $targetType = $symbolObject->TypeDefinedIn($file);
+            $targetPrototype = $symbolObject->PrototypeDefinedIn($file);
+            $targetSummary = $symbolObject->SummaryDefinedIn($file);
+            }
+        else
+            {
+            $targetFile = $symbolObject->GlobalDefinition();
+            $targetType = $symbolObject->GlobalType();
+            $targetPrototype = $symbolObject->GlobalPrototype();
+            $targetSummary = $symbolObject->GlobalSummary();
+            };
+
+        return NaturalDocs::SymbolTable::ReferenceTarget->New($symbol, $targetFile, $targetType, $targetPrototype,
+                                                                                             $targetSummary);
+        }
+
+    else
+        {  return undef;  };
+    };
+
+
+#
+#   Function: Index
+#
+#   Returns a symbol index.
+#
+#   Indexes are generated on demand, but they are stored so subsequent calls for the same index will be fast.  Call
+#   <PurgeIndexes()> to clear the generated indexes.
+#
+#   Parameters:
+#
+#       type  - The <TopicType> of symbol to limit the index to, or undef for none.
+#
+#   Returns:
+#
+#       An arrayref of sections.  The first represents all the symbols, the second the numbers, and the rest A through Z.
+#       Each section is a sorted arrayref of <NaturalDocs::SymbolTable::IndexElement> objects.  If a section has no content,
+#       it will be undef.
+#
+sub Index #(type)
+    {
+    my ($self, $type) = @_;
+
+    if (!exists $indexes{$type})
+        {  $indexes{$type} = $self->MakeIndex($type);  };
+
+    return $indexes{$type};
+    };
+
+
+#
+#   Function: HasIndexes
+#
+#   Determines which indexes out of a list actually have content.
+#
+#   Parameters:
+#
+#       types  - An existence hashref of the <TopicTypes> to check for indexes.
+#
+#   Returns:
+#
+#       An existence hashref of all the specified indexes that have content.  Will return an empty hashref if none.
+#
+sub HasIndexes #(types)
+    {
+    my ($self, $types) = @_;
+
+    # EliminationHash is a copy of all the types, and the types will be deleted as they are found.  This allows us to quit early if
+    # we've found all the types because the hash will be empty.  We'll later return the original hash minus what was left over
+    # in here, which are the ones that weren't found.
+    my %eliminationHash = %$types;
+
+    finddefs:
+    foreach my $symbolObject (values %symbols)
+        {
+        foreach my $definition ($symbolObject->Definitions())
+            {
+            delete $eliminationHash{ $symbolObject->TypeDefinedIn($definition) };
+            delete $eliminationHash{ ::TOPIC_GENERAL() };
+
+            if (!scalar keys %eliminationHash)
+                {  last finddefs;  };
+            };
+        };
+
+    my $result = { %$types };
+
+    foreach my $type (keys %eliminationHash)
+        {  delete $result->{$type};  };
+
+    return $result;
+    };
+
+
+#
+#   Function: IndexChanged
+#
+#   Returns whether the specified index has changed.
+#
+#   Parameters:
+#
+#       type  - The <TopicType> to limit the index to.
+#
+sub IndexChanged #(TopicType type)
+    {
+    my ($self, $type) = @_;
+    return ($rebuildIndexes || defined $indexChanges{$type});
+    };
+
+
+#
+#   Function: IndexSectionsWithContent
+#
+#   Returns an arrayref of whether each section of the specified index has content.  The first entry will be for symbols, the second
+#   for numbers, and the rest A-Z.  Do not change the arrayref.
+#
+sub IndexSectionsWithContent #(TopicType type)
+    {
+    my ($self, $type) = @_;
+
+    if (!exists $indexSectionsWithContent{$type})
+        {
+        # This is okay because Index() stores generated indexes.  It's not an expensive operation unless the index was never asked
+        # for before or it will never be asked for otherwise, and this shouldn't be the case.
+
+        my $index = $self->Index($type);
+        my $content = [ ];
+
+        for (my $i = 0; $i < 28; $i++)
+            {
+            push @$content, (defined $index->[$i] ? 1 : 0);
+            };
+
+        $indexSectionsWithContent{$type} = $content;
+        };
+
+    return $indexSectionsWithContent{$type};
+    };
+
+
+
+###############################################################################
+# Group: Event Handlers
+
+
+#
+#   Function: OnIndexChange
+#
+#   Called whenever a change happens to a symbol that would cause an index to be regenerated.
+#
+#   Parameters:
+#
+#       type - The <TopicType> of the symbol that caused the change.
+#
+sub OnIndexChange #(TopicType type)
+    {
+    my ($self, $type) = @_;
+
+    $indexChanges{$type} = 1;
+    $indexChanges{::TOPIC_GENERAL()} = 1;
+    delete $indexSectionsWithContent{$type};
+    };
+
+
+#
+#   Function: OnInterpretationChange
+#
+#   Called whenever the current interpretation of a reference changes, meaning it switched from one symbol to another.
+#
+#   Parameters:
+#
+#       referenceString - The <ReferenceString> whose current interpretation changed.
+#
+sub OnInterpretationChange #(referenceString)
+    {
+    my ($self, $referenceString) = @_;
+    my $referenceType = NaturalDocs::ReferenceString->TypeOf($referenceString);
+
+    if ($referenceType == ::REFERENCE_TEXT())
+        {
+        my @referenceDefinitions = $references{$referenceString}->Definitions();
+
+        foreach my $referenceDefinition (@referenceDefinitions)
+            {
+            NaturalDocs::Project->RebuildFile($referenceDefinition);
+            };
+        }
+
+    elsif (NaturalDocs::Constants->IsClassHierarchyReference($referenceType))
+        {
+        NaturalDocs::ClassHierarchy->OnInterpretationChange($referenceString);
+        };
+    };
+
+
+#
+#   Function: OnTargetSymbolChange
+#
+#   Called whenever the symbol that serves as the interpretation of a reference changes, but the reference still resolves to
+#   the same symbol.  This would happen if the type, prototype, summary, or which file serves as global definition of the symbol
+#   changes.
+#
+#   Parameters:
+#
+#       referenceString - The <ReferenceString> whose interpretation's symbol changed.
+#
+sub OnTargetSymbolChange #(referenceString)
+    {
+    my ($self, $referenceString) = @_;
+    my $referenceType = NaturalDocs::ReferenceString->TypeOf($referenceString);
+
+    if ($referenceType == ::REFERENCE_TEXT())
+        {
+        my @referenceDefinitions = $references{$referenceString}->Definitions();
+
+        foreach my $referenceDefinition (@referenceDefinitions)
+            {
+            NaturalDocs::Project->RebuildFile($referenceDefinition);
+            };
+        }
+
+    elsif (NaturalDocs::Constants->IsClassHierarchyReference($referenceType))
+        {
+        NaturalDocs::ClassHierarchy->OnTargetSymbolChange($referenceString);
+        };
+    };
+
+
+
+###############################################################################
+# Group: Support Functions
+
+
+#
+#   Function: DeleteSymbol
+#
+#   Removes a symbol definition from the table.  It will call <OnInterpretationChange()> for all references that have it as their
+#   current interpretation.
+#
+#   External code should not attempt to delete symbols using this function.  Instead it should call <WatchFileFoChanges()>,
+#   reparse the file, and call <AnalyzeChanges()>.
+#
+#   Parameters:
+#
+#       symbol - The <SymbolString>.
+#       file       - The <FileName> where the definition is.
+#
+sub DeleteSymbol #(symbol, file)
+    {
+    my ($self, $symbol, $file) = @_;
+
+
+    # If the symbol and definition exist...
+    if (exists $symbols{$symbol} && $symbols{$symbol}->IsDefinedIn($file))
+        {
+        my $symbolObject = $symbols{$symbol};
+        my $wasGlobal = ($symbolObject->GlobalDefinition() eq $file);
+
+        $self->OnIndexChange($symbolObject->TypeDefinedIn($file));
+
+        $symbolObject->DeleteDefinition($file);
+
+        # If this was one definition of many...
+        if ($symbolObject->IsDefined())
+            {
+
+            # If this was the global definition...
+            if ($wasGlobal)
+                {
+                # Update every file that referenced the global symbol; i.e. every file that doesn't have its own definition.
+
+                my @references = $symbolObject->References();
+
+                foreach my $reference (@references)
+                    {
+                    if ($references{$reference}->CurrentInterpretation() eq $symbol)
+                        {
+                        $self->OnTargetSymbolChange($reference);
+                        };
+                    };
+                }
+
+            # If this wasn't the global definition...
+            else
+                {
+                # It's a safe bet that we don't need to do anything here.  The only thing that we even need to look for here is if the
+                # file referenced its own symbol and thus should be rebuilt.  However, if the file is having a symbol deleted, it either
+                # changed or was itself deleted.  If it changed and still has other Natural Docs content, it should already be on the
+                # rebuild list.  If it was deleted or no longer has Natural Docs content, we certainly don't want to add it to the rebuild
+                # list.
+                };
+            }
+
+        # If this is the only definition...
+        else
+            {
+            # If this symbol is the interpretation of any references...
+            if ($symbolObject->HasReferences())
+                {
+                # If this was the current interpretation of any references, reinterpret them and rebuild their files.
+
+                my @references = $symbolObject->References();
+
+                foreach my $reference (@references)
+                    {
+                    if ($references{$reference}->CurrentInterpretation() eq $symbol)
+                        {
+                        $self->InterpretReference($reference);
+                        $self->OnInterpretationChange($reference);
+                        };
+                    };
+                }
+
+            # If there are no interpretations of the symbol...
+            else
+                {
+                # Delete the symbol entirely.
+                delete $symbols{$symbol};
+                };
+            };
+
+        # Remove it from the file index.
+
+        $files{$file}->DeleteSymbol($symbol);
+
+        if (!$files{$file}->HasAnything())
+            {  delete $files{$file};  };
+
+
+        # We don't need to worry about the watched file, since this function will only be called by AnalyzeChanges() and
+        # LoadAndPurge().
+        };
+    };
+
+
+#
+#   Function: GenerateInterpretations
+#
+#   Generates the list of interpretations for the passed reference.  Also creates potential symbols as necessary.
+#
+#   Parameters:
+#
+#       referenceString - The <ReferenceString> to generate the interpretations of.
+#
+sub GenerateInterpretations #(referenceString)
+    {
+    my ($self, $referenceString) = @_;
+
+    my ($type, $symbol, $languageName, $scope, $using, $resolvingFlags) =
+        NaturalDocs::ReferenceString->InformationOf($referenceString);
+
+    # RESOLVE_NOPLURAL is handled by having @singulars be empty.
+    my @singulars;
+    if (!($resolvingFlags & ::RESOLVE_NOPLURAL()))
+        {  @singulars = $self->SingularInterpretationsOf($symbol);  };
+
+    # Since higher scores are better, we'll start at a high number and decrement.
+    my $score = 50000;
+
+
+    # If RESOLVE_RELATIVE is set, we do all the scope relatives before the global.
+    if ($resolvingFlags & ::RESOLVE_RELATIVE())
+        {
+        $score = $self->GenerateRelativeInterpretations($referenceString, $symbol, \@singulars, $scope, $score);
+        }
+
+    # If neither RESOLVE_RELATIVE nor RESOLVE_ABSOLUTE is set, we only do the local before the global.
+    elsif (!($resolvingFlags & ::RESOLVE_ABSOLUTE()))
+        {
+        $self->AddInterpretation($referenceString, NaturalDocs::SymbolString->Join($scope, $symbol), $score);
+        $score--;
+
+        foreach my $singular (@singulars)
+            {
+            $self->AddInterpretation($referenceString, NaturalDocs::SymbolString->Join($scope, $singular), $score);
+            $score--;
+            };
+        };
+
+
+    # Do the global.
+
+    $self->AddInterpretation($referenceString, $symbol, $score);
+    $score--;
+
+    foreach my $singular (@singulars)
+        {
+        $self->AddInterpretation($referenceString, $singular, $score);
+        $score--;
+        };
+
+
+    # If neither RESOLVE_RELATIVE nor RESOLVE_ABSOLUTE is set, we need to do the rest of the scope relatives after the global.
+    if (!($resolvingFlags & ::RESOLVE_RELATIVE()) && !($resolvingFlags & ::RESOLVE_ABSOLUTE()))
+        {
+        $score = $self->GenerateRelativeInterpretations($referenceString, $symbol, \@singulars, $scope, $score, 1);
+        };
+
+
+    # Finally, if RESOLVE_NOUSING isn't set, go through the using scopes.
+    if (!($resolvingFlags & ::RESOLVE_NOUSING()) && defined $using)
+        {
+        foreach my $usingScope (@$using)
+            {
+            if ($resolvingFlags & ::RESOLVE_ABSOLUTE())
+                {
+                $self->AddInterpretation($referenceString, NaturalDocs::SymbolString->Join($usingScope, $symbol), $score);
+                $score--;
+
+                foreach my $singular (@singulars)
+                    {
+                    $self->AddInterpretation($referenceString, NaturalDocs::SymbolString->Join($usingScope, $singular), $score);
+                    $score--;
+                    };
+                }
+            else
+                {
+                $score = $self->GenerateRelativeInterpretations($referenceString, $symbol, \@singulars, $usingScope, $score);
+                };
+            };
+        };
+    };
+
+
+#
+#   Function: GenerateRelativeInterpretations
+#
+#   Generates the list of relative interpretations for the passed reference and packages.  Also creates potential symbols as
+#   necessary.
+#
+#   This function will _not_ create global interpretations.  It _will_ create a local interpretations (symbol + all packages) unless
+#   you set dontUseFull.
+#
+#   Parameters:
+#
+#       referenceString - The <ReferenceString> to generate interpretations for.
+#       symbol - The <SymbolString> to generate interpretations of.
+#       singulars - A reference to an array of singular <SymbolStrings> to also generate interpretations of.  Set to an empty array
+#                       if none.
+#       package - The package <SymbolString> to use.  May be undef.
+#       score - The starting score to apply.
+#       dontUseFull - Whether to not generate an interpretation including the full package identifier.  If set, generated interpretations
+#                           will start one level down.
+#
+#   Returns:
+#
+#       The next unused score.  This is basically the passed score minus the number of interpretations created.
+#
+sub GenerateRelativeInterpretations #(referenceString, symbol, singulars, package, score, dontUseFull)
+    {
+    my ($self, $referenceString, $symbol, $singulars, $package, $score, $dontUseFull) = @_;
+
+    my @packages = NaturalDocs::SymbolString->IdentifiersOf($package);
+
+    # The last package index to include.  This number is INCLUSIVE!
+    my $packageLevel = scalar @packages - 1;
+
+    if ($dontUseFull)
+        {  $packageLevel--;  };
+
+    while ($packageLevel >= 0)
+        {
+        $self->AddInterpretation($referenceString, NaturalDocs::SymbolString->Join(@packages[0..$packageLevel], $symbol),
+                                             $score);
+        $score--;
+
+        foreach my $singular (@$singulars)
+            {
+            $self->AddInterpretation($referenceString, NaturalDocs::SymbolString->Join(@packages[0..$packageLevel], $singular),
+                                                 $score);
+            $score--;
+            };
+
+        $packageLevel--;
+        };
+
+    return $score;
+    };
+
+
+#
+#   Function: SingularInterpretationsOf
+#
+#   Generates singular interpretations of a <SymbolString> if it can be interpreted as a plural.  Not all of them will be valid singular
+#   forms, but that doesn't matter since it's incredibly unlikely an invalid form would exist as a symbol.  What matters is that the
+#   legimate singular is present on the list.
+#
+#   Parameters:
+#
+#       symbol - The <SymbolString>.
+#
+#   Returns:
+#
+#       An array of potential singular interpretations as <SymbolStrings>, in no particular order.  If the symbol can't be interpreted
+#       as a plural, returns an empty array.
+#
+sub SingularInterpretationsOf #(symbol)
+    {
+    my ($self, $symbol) = @_;
+
+    my @identifiers = NaturalDocs::SymbolString->IdentifiersOf($symbol);
+    my $lastIdentifier = pop @identifiers;
+    my $preIdentifiers = NaturalDocs::SymbolString->Join(@identifiers);
+
+    my @results;
+
+    # First cut off any 's or ' at the end, since they can appear after other plural forms.
+    if ($lastIdentifier =~ s/\'s?$//i)
+        {
+        push @results, NaturalDocs::SymbolString->Join($preIdentifiers, $lastIdentifier);
+        };
+
+    # See http://www.gsu.edu/~wwwesl/egw/crump.htm for a good list of potential plural forms.  There are a couple more than
+    # listed below, but they're fairly rare and this is already seriously over-engineered.  This is split by suffix length to make
+    # comparisons more efficient.
+
+    # The fact that this will generate some impossible combinations (leaves => leave, leav, leaf, leafe) doesn't matter.  It's very
+    # unlikely that more than one will manage to match a defined symbol.  Even if they do (leave, leaf), it's incredibly unlikely
+    # that someone has defined an impossible one (leav, leafe).  So it's not so important that we remove impossible combinations,
+    # just that we include all the possible ones.
+
+    my @suffixGroups = ( [ 's', undef,  # boys => boy
+                                       'i', 'us',  # alumni => alumnus
+                                       'a', 'um', # errata => erratum
+                                       'a', 'on' ],  # phenomena => phenomenon
+
+                                    [ 'es', undef,  # foxes => fox
+                                      'ae', 'a' ],  # amoebae => amoeba
+
+                                    [ 'ies', 'y',  # pennies => penny
+                                      'ves', 'f',  # calves => calf
+                                      'ves', 'fe',  # knives => knife
+                                      'men', 'man',  # women => woman
+                                      'ice', 'ouse',  # mice => mouse
+                                      'oes', 'o',  # vetoes => veto
+                                      'ces', 'x',  # matrices => matrix
+                                      'xen', 'x' ],  # oxen => ox
+
+                                    [ 'ices', 'ex',  # indices => index
+                                      'feet', 'foot',  # feet => foot
+                                      'eese', 'oose',  # geese => goose
+                                      'eeth', 'ooth',  # teeth => tooth
+                                      'dren', 'd' ] );  # children => child
+
+    my $suffixLength = 1;
+
+    foreach my $suffixGroup (@suffixGroups)
+        {
+        my $identifierSuffix = lc( substr($lastIdentifier, 0 - $suffixLength) );
+        my $cutIdentifier = substr($lastIdentifier, 0, 0 - $suffixLength);
+
+        for (my $i = 0; $i + 1 < scalar @$suffixGroup; $i += 2)
+            {
+            my $suffix = $suffixGroup->[$i];
+            my $replacement = $suffixGroup->[$i + 1];
+
+            if ($identifierSuffix eq $suffix)
+                {
+                if (defined $replacement)
+                    {
+                    push @results, NaturalDocs::SymbolString->Join($preIdentifiers, $cutIdentifier . $replacement);
+                    push @results, NaturalDocs::SymbolString->Join($preIdentifiers, $cutIdentifier . uc($replacement));
+                    }
+                else
+                    {
+                    push @results, NaturalDocs::SymbolString->Join($preIdentifiers, $cutIdentifier);
+                    };
+                };
+            };
+
+        $suffixLength++;
+        };
+
+    return @results;
+    };
+
+
+#
+#   Function: AddInterpretation
+#
+#   Adds an interpretation to an existing reference.  Creates potential symbols as necessary.
+#
+#   Parameters:
+#
+#       referenceString - The <ReferenceString> to add the interpretation to.
+#       symbol - The <SymbolString> the reference can be interpreted as.
+#       score - The score of the interpretation.
+#
+sub AddInterpretation #(referenceString, symbol, score)
+    {
+    my ($self, $referenceString, $symbol, $score) = @_;
+
+    $references{$referenceString}->AddInterpretation($symbol, $score);
+
+    # Create a potential symbol if it doesn't exist.
+
+    if (!exists $symbols{$symbol})
+        {  $symbols{$symbol} = NaturalDocs::SymbolTable::Symbol->New();  };
+
+    $symbols{$symbol}->AddReference($referenceString, $score);
+    };
+
+
+#
+#   Function: InterpretReference
+#
+#   Interprets the passed reference, matching it to the defined symbol with the highest score.  If the symbol is already
+#   interpreted, it will reinterpret it.  If there are no matches, it will make it an undefined reference.
+#
+#   Parameters:
+#
+#       referenceString - The <ReferenceString> to interpret.
+#
+sub InterpretReference #(referenceString)
+    {
+    my ($self, $referenceString) = @_;
+
+    my $interpretation;
+    my $currentInterpretation;
+    my $score;
+    my $currentScore = -1;
+
+    my $referenceObject = $references{$referenceString};
+
+    my %interpretationsAndScores = $referenceObject->InterpretationsAndScores();
+    while ( ($interpretation, $score) = each %interpretationsAndScores )
+        {
+        if ($score > $currentScore && $symbols{$interpretation}->IsDefined())
+            {
+            $currentScore = $score;
+            $currentInterpretation = $interpretation;
+            };
+        };
+
+    if ($currentScore > -1)
+        {  $referenceObject->SetCurrentInterpretation($currentInterpretation);  }
+    else
+        {  $referenceObject->SetCurrentInterpretation(undef);  };
+    };
+
+
+#
+#   Function: MakeIndex
+#
+#   Generates a symbol index.
+#
+#   Parameters:
+#
+#       type  - The <TopicType> to limit the index to.
+#
+#   Returns:
+#
+#       An arrayref of sections.  The first represents all the symbols, the second the numbers, and the rest A through Z.
+#       Each section is a sorted arrayref of <NaturalDocs::SymbolTable::IndexElement> objects.  If a section has no content,
+#       it will be undef.
+#
+sub MakeIndex #(type)
+    {
+    my ($self, $type) = @_;
+
+
+    # Go through the symbols and generate IndexElements for any that belong in the index.
+
+    # Keys are the symbol strings, values are IndexElements.
+    my %indexSymbols;
+
+    while (my ($symbolString, $object) = each %symbols)
+        {
+        my ($symbol, $package) = $self->SplitSymbolForIndex($symbolString, $object->GlobalType());
+        my @definitions = $object->Definitions();
+
+        foreach my $definition (@definitions)
+            {
+            my $definitionType = $object->TypeDefinedIn($definition);
+
+            if ($type eq ::TOPIC_GENERAL() || $type eq $definitionType)
+                {
+                if (!exists $indexSymbols{$symbol})
+                    {
+                    $indexSymbols{$symbol} =
+                        NaturalDocs::SymbolTable::IndexElement->New($symbol, $package, $definition, $definitionType,
+                                                                                               $object->PrototypeDefinedIn($definition),
+                                                                                               $object->SummaryDefinedIn($definition) );
+                    }
+                else
+                    {
+                    $indexSymbols{$symbol}->Merge($package, $definition, $definitionType,
+                                                                       $object->PrototypeDefinedIn($definition),
+                                                                       $object->SummaryDefinedIn($definition) );
+                    };
+                }; # If type matches
+            }; # Each definition
+        }; # Each symbol
+
+
+    # Generate sortable symbols for each IndexElement, sort them internally, and divide them into sections.
+
+    my $sections = [ ];
+
+    foreach my $indexElement (values %indexSymbols)
+        {
+        $indexElement->Sort();
+        $indexElement->MakeSortableSymbol();
+
+        my $sectionNumber;
+
+        if ($indexElement->SortableSymbol() =~ /^([a-z])/i)
+            {  $sectionNumber = ord(lc($1)) - ord('a') + 2;  }
+        elsif ($indexElement->SortableSymbol() =~ /^[0-9]/)
+            {  $sectionNumber = 1;  }
+        else
+            {  $sectionNumber = 0;  };
+
+        if (!defined $sections->[$sectionNumber])
+            {  $sections->[$sectionNumber] = [ ];  };
+
+        push @{$sections->[$sectionNumber]}, $indexElement;
+        };
+
+
+    # Sort each section.
+
+    for (my $i = 0; $i < scalar @$sections; $i++)
+        {
+        if (defined $sections->[$i])
+            {
+            @{$sections->[$i]} = sort
+                {
+                my $result = ::StringCompare($a->SortableSymbol(), $b->SortableSymbol());
+
+                if ($result == 0)
+                    {  $result = ::StringCompare($a->IgnoredPrefix(), $b->IgnoredPrefix());  };
+
+                return $result;
+                }
+            @{$sections->[$i]};
+            };
+        };
+
+    return $sections;
+    };
+
+
+#
+#   Function: SplitSymbolForIndex
+#
+#   Splits a <SymbolString> into its symbol and package portions for indexing.
+#
+#   Parameters:
+#
+#       symbol - The <SymbolString>.
+#       type - Its <TopicType>.
+#
+#   Returns:
+#
+#       The array ( symbol, package ), which are both <SymbolStrings>.  If the symbol is global, package will be undef.
+#
+sub SplitSymbolForIndex #(symbol, type)
+    {
+    my ($self, $symbol, $type) = @_;
+
+    my $scope = NaturalDocs::Topics->TypeInfo($type)->Scope();
+
+    if ($scope == ::SCOPE_START() || $scope == ::SCOPE_ALWAYS_GLOBAL())
+        {  return ( $symbol, undef );  }
+    else
+        {
+        my @identifiers = NaturalDocs::SymbolString->IdentifiersOf($symbol);
+
+        $symbol = pop @identifiers;
+        my $package = NaturalDocs::SymbolString->Join(@identifiers);
+
+        return ( $symbol, $package );
+        };
+    };
+
+
+1;

Added: trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Topics/Type.pm
===================================================================
--- trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Topics/Type.pm	                        (rev 0)
+++ trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Topics/Type.pm	2009-07-01 15:30:00 UTC (rev 4275)
@@ -0,0 +1,151 @@
+###############################################################################
+#
+#   Package: NaturalDocs::Topics::Type
+#
+###############################################################################
+#
+#   A class storing information about a <TopicType>.
+#
+###############################################################################
+
+# 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::Topics::Type;
+
+use NaturalDocs::DefineMembers 'NAME',                         'Name()',
+                                                 'PLURAL_NAME',             'PluralName()',      'SetPluralName()',
+                                                 'INDEX',                        'Index()',              'SetIndex()',
+                                                 'SCOPE',                       'Scope()',              'SetScope()',
+                                                 'PAGE_TITLE_IF_FIRST', 'PageTitleIfFirst()', 'SetPageTitleIfFirst()',
+                                                 'BREAK_LISTS',             'BreakLists()',        'SetBreakLists()',
+                                                 'CLASS_HIERARCHY',    'ClassHierarchy()',  'SetClassHierarchy()',
+                                                 'CAN_GROUP_WITH';
+
+# Dependency: New() depends on the order of these and that there are no parent classes.
+
+use base 'Exporter';
+our @EXPORT = ('SCOPE_NORMAL', 'SCOPE_START', 'SCOPE_END', 'SCOPE_ALWAYS_GLOBAL');
+
+#
+#   Constants: Members
+#
+#   The object is implemented as a blessed arrayref, with the following constants as its indexes.
+#
+#   NAME - The topic's name.
+#   PLURAL_NAME - The topic's plural name.
+#   INDEX - Whether the topic is indexed.
+#   SCOPE - The topic's <ScopeType>.
+#   PAGE_TITLE_IF_FIRST - Whether the topic becomes the page title if it's first in a file.
+#   BREAK_LISTS - Whether list topics should be broken into individual topics in the output.
+#   CLASS_HIERARCHY - Whether the topic is part of the class hierarchy.
+#   CAN_GROUP_WITH - The existence hashref of <TopicTypes> the type can be grouped with.
+#
+
+
+
+###############################################################################
+# Group: Types
+
+
+#
+#   Constants: ScopeType
+#
+#   The possible values for <Scope()>.
+#
+#   SCOPE_NORMAL - The topic stays in the current scope without affecting it.
+#   SCOPE_START - The topic starts a scope.
+#   SCOPE_END - The topic ends a scope, returning it to global.
+#   SCOPE_ALWAYS_GLOBAL - The topic is always global, but it doesn't affect the current scope.
+#
+use constant SCOPE_NORMAL => 1;
+use constant SCOPE_START => 2;
+use constant SCOPE_END => 3;
+use constant SCOPE_ALWAYS_GLOBAL => 4;
+
+
+
+###############################################################################
+# Group: Functions
+
+
+#
+#   Function: New
+#
+#   Creates and returns a new object.
+#
+#   Parameters:
+#
+#       name - The topic name.
+#       pluralName - The topic's plural name.
+#       index - Whether the topic is indexed.
+#       scope - The topic's <ScopeType>.
+#       pageTitleIfFirst - Whether the topic becomes the page title if it's the first one in a file.
+#       breakLists - Whether list topics should be broken into individual topics in the output.
+#
+sub New #(name, pluralName, index, scope, pageTitleIfFirst, breakLists)
+    {
+    my ($self, @params) = @_;
+
+    # Dependency: Depends on the parameter order matching the member order and that there are no parent classes.
+
+    my $object = [ @params ];
+    bless $object, $self;
+
+    return $object;
+    };
+
+
+#
+#   Functions: Accessors
+#
+#   Name - Returns the topic name.
+#   PluralName - Returns the topic's plural name.
+#   SetPluralName - Replaces the topic's plural name.
+#   Index - Whether the topic is indexed.
+#   SetIndex - Sets whether the topic is indexed.
+#   Scope - Returns the topic's <ScopeType>.
+#   SetScope - Replaces the topic's <ScopeType>.
+#   PageTitleIfFirst - Returns whether the topic becomes the page title if it's first in the file.
+#   SetPageTitleIfFirst - Sets whether the topic becomes the page title if it's first in the file.
+#   BreakLists - Returns whether list topics should be broken into individual topics in the output.
+#   SetBreakLists - Sets whether list topics should be broken into individual topics in the output.
+#   ClassHierarchy - Returns whether the topic is part of the class hierarchy.
+#   SetClassHierarchy - Sets whether the topic is part of the class hierarchy.
+#
+
+
+#
+#   Function: CanGroupWith
+#
+#   Returns whether the type can be grouped with the passed <TopicType>.
+#
+sub CanGroupWith #(TopicType type) -> bool
+    {
+    my ($self, $type) = @_;
+    return ( defined $self->[CAN_GROUP_WITH] && exists $self->[CAN_GROUP_WITH]->{$type} );
+    };
+
+
+#
+#   Function: SetCanGroupWith
+#
+#   Sets the list of <TopicTypes> the type can be grouped with.
+#
+sub SetCanGroupWith #(TopicType[] types)
+    {
+    my ($self, $types) = @_;
+
+    $self->[CAN_GROUP_WITH] = { };
+
+    foreach my $type (@$types)
+        {  $self->[CAN_GROUP_WITH]->{$type} = 1;  };
+    };
+
+
+
+1;

Added: trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Topics.pm
===================================================================
--- trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Topics.pm	                        (rev 0)
+++ trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Topics.pm	2009-07-01 15:30:00 UTC (rev 4275)
@@ -0,0 +1,1319 @@
+###############################################################################
+#
+#   Package: NaturalDocs::Topics
+#
+###############################################################################
+#
+#   The topic constants and functions to convert them to and from strings used throughout the script.  All constants are exported
+#   by default.
+#
+###############################################################################
+
+# This file is part of Natural Docs, which is Copyright (C) 2003-2008 Greg Valure
+# Natural Docs is licensed under the GPL
+
+use Text::Wrap ( );
+use Tie::RefHash ( );
+
+use strict;
+use integer;
+
+use NaturalDocs::Topics::Type;
+
+package NaturalDocs::Topics;
+
+use base 'Exporter';
+our @EXPORT = ( 'TOPIC_GENERAL', 'TOPIC_GENERIC', 'TOPIC_GROUP', 'TOPIC_CLASS', 'TOPIC_FILE', 'TOPIC_FUNCTION',
+                          'TOPIC_VARIABLE', 'TOPIC_PROPERTY', 'TOPIC_TYPE', 'TOPIC_ENUMERATION', 'TOPIC_CONSTANT',
+                          'TOPIC_INTERFACE', 'TOPIC_EVENT', 'TOPIC_DELEGATE', 'TOPIC_SECTION' );
+
+
+
+###############################################################################
+# Group: Types
+
+
+#
+#   Type: TopicType
+#
+#   A string representing a topic type as defined in <Topics.txt>.  It's format should be treated as opaque; use <MakeTopicType()>
+#   to get them from topic names.  However, they can be compared for equality with string functions.
+#
+
+
+#
+#   Constants: Default TopicTypes
+#
+#   Exported constants of the default <TopicTypes>, so you don't have to go through <TypeFromName()> every time.
+#
+#   TOPIC_GENERAL - The general <TopicType>, which has the special meaning of none in particular.
+#   TOPIC_GENERIC - Generic <TopicType>.
+#   TOPIC_GROUP - Group <TopicType>.
+#   TOPIC_CLASS - Class <TopicType>.
+#   TOPIC_INTERFACE - Interface <TopicType>.
+#   TOPIC_FILE - File <TopicType>.
+#   TOPIC_SECTION - Section <TopicType>.
+#   TOPIC_FUNCTION - Function <TopicType>.
+#   TOPIC_VARIABLE - Variable <TopicType>.
+#   TOPIC_PROPERTY - Property <TopicType>.
+#   TOPIC_TYPE - Type <TopicType>.
+#   TOPIC_CONSTANT - Constant <TopicType>.
+#   TOPIC_ENUMERATION - Enum <TopicType>.
+#   TOPIC_DELEGATE - Delegate <TopicType>.
+#   TOPIC_EVENT - Event <TopicType>.
+#
+use constant TOPIC_GENERAL => 'general';
+use constant TOPIC_GENERIC => 'generic';
+use constant TOPIC_GROUP => 'group';
+use constant TOPIC_CLASS => 'class';
+use constant TOPIC_INTERFACE => 'interface';
+use constant TOPIC_FILE => 'file';
+use constant TOPIC_SECTION => 'section';
+use constant TOPIC_FUNCTION => 'function';
+use constant TOPIC_VARIABLE => 'variable';
+use constant TOPIC_PROPERTY => 'property';
+use constant TOPIC_TYPE => 'type';
+use constant TOPIC_CONSTANT => 'constant';
+use constant TOPIC_ENUMERATION => 'enumeration';
+use constant TOPIC_DELEGATE => 'delegate';
+use constant TOPIC_EVENT => 'event';
+# Dependency: The values of these constants must match what is generated by MakeTopicType().
+# Dependency: These types must be added to requiredTypeNames so that they always exist.
+
+
+
+
+###############################################################################
+# Group: Variables
+
+
+#
+#   handle: FH_TOPICS
+#
+#   The file handle used when writing to <Topics.txt>.
+#
+
+
+#
+#   hash: types
+#
+#   A hashref that maps <TopicTypes> to <NaturalDocs::Topics::Type>s.
+#
+my %types;
+
+
+#
+#   hash: names
+#
+#   A hashref that maps various forms of the all-lowercase type names to <TopicTypes>.  All are in the same hash because
+#   two names that reduce to the same thing it would cause big problems, and we need to catch that.  Keys include
+#
+#   - Topic names
+#   - Plural topic names
+#   - Alphanumeric-only topic names
+#   - Alphanumeric-only plural topic names
+#
+my %names;
+
+
+#
+#   hash: keywords
+#
+#   A hashref that maps all-lowercase keywords to their <TopicTypes>.  Must not have any of the same keys as
+#   <pluralKeywords>.
+#
+my %keywords;
+
+
+#
+#   hash: pluralKeywords
+#
+#   A hashref that maps all-lowercase plural keywords to their <TopicTypes>.  Must not have any of the same keys as
+#   <keywords>.
+#
+my %pluralKeywords;
+
+
+#
+#   hash: indexable
+#
+#   An existence hash of all the indexable <TopicTypes>.
+#
+my %indexable;
+
+
+#
+#   array: requiredTypeNames
+#
+#   An array of the <TopicType> names which are required to be defined in the main file.  Are in the order they should appear
+#   when reformatting.
+#
+my @requiredTypeNames = ( 'Generic', 'Class', 'Interface', 'Section', 'File', 'Group', 'Function', 'Variable', 'Property', 'Type',
+                                           'Constant', 'Enumeration', 'Event', 'Delegate' );
+
+
+#
+#   array: legacyTypes
+#
+#   An array that converts the legacy topic types, which were numeric constants prior to 1.3, to the current <TopicTypes>.
+#   The legacy types are used as an index into the array.  Note that this does not support list type values.
+#
+my @legacyTypes = ( TOPIC_GENERAL, TOPIC_CLASS, TOPIC_SECTION, TOPIC_FILE, TOPIC_GROUP, TOPIC_FUNCTION,
+                                TOPIC_VARIABLE, TOPIC_GENERIC, TOPIC_TYPE, TOPIC_CONSTANT, TOPIC_PROPERTY );
+
+
+#
+#   array: mainTopicNames
+#
+#   An array of the <TopicType> names that are defined in the main <Topics.txt>.
+#
+my @mainTopicNames;
+
+
+
+###############################################################################
+# Group: Files
+
+
+#
+#   File: Topics.txt
+#
+#   The configuration file that defines or overrides the topic definitions for Natural Docs.  One version sits in Natural Docs'
+#   configuration directory, and another can be in a project directory to add to or override them.
+#
+#   > # [comments]
+#
+#   Everything after a # symbol is ignored.
+#
+#   Except when specifying topic names, everything below is case-insensitive.
+#
+#   > Format: [version]
+#
+#   Specifies the file format version of the file.
+#
+#
+#   Sections:
+#
+#       > Ignore[d] Keyword[s]: [keyword], [keyword] ...
+#       >    [keyword]
+#       >    [keyword], [keyword]
+#       >    ...
+#
+#       Ignores the keywords so that they're not recognized as Natural Docs topics anymore.  Can be specified as a list on the same
+#       line and/or following like a normal Keywords section.
+#
+#       > Topic Type: [name]
+#       > Alter Topic Type: [name]
+#
+#       Creates a new topic type or alters an existing one.  The name can only contain <CFChars> and isn't case sensitive, although
+#       the original case is remembered for presentation.
+#
+#       The name General is reserved.  There are a number of default types that must be defined in the main file as well, but those
+#       are governed by <NaturalDocs::Topics> and are not included here.  The default types can have their keywords or behaviors
+#       changed, though, either by editing the default file or by overriding them in the user file.
+#
+#       Enumeration is a special type.  It is indexed with Types and its definition list members are listed with Constants according
+#       to the rules in <Languages.txt>.
+#
+#
+#   Topic Type Sections:
+#
+#       > Plural: [name]
+#
+#       Specifies the plural name of the topic type.  Defaults to the singular name.  Has the same restrictions as the topic type
+#       name.
+#
+#       > Index: [yes|no]
+#
+#       Whether the topic type gets an index.  Defaults to yes.
+#
+#       > Scope: [normal|start|end|always global]
+#
+#       How the topic affects scope.  Defaults to normal.
+#
+#       normal - The topic stays within the current scope.
+#       start - The topic starts a new scope for all the topics beneath it, like class topics.
+#       end - The topic resets the scope back to global for all the topics beneath it, like section topics.
+#       always global - The topic is defined as a global symbol, but does not change the scope for any other topics.
+#
+#       > Class Hierarchy: [yes|no]
+#
+#       Whether the topic is part of the class hierarchy.  Defaults to no.
+#
+#       > Page Title if First: [yes|no]
+#
+#       Whether the title of this topic becomes the page title if it is the first topic in a file.  Defaults to no.
+#
+#       > Break Lists: [yes|no]
+#
+#       Whether list topics should be broken into individual topics in the output.  Defaults to no.
+#
+#       > Can Group With: [topic type], [topic type], ...
+#
+#       The list of <TopicTypes> the topic can possibly be grouped with.
+#
+#       > [Add] Keyword[s]:
+#       >    [keyword]
+#       >    [keyword], [plural keyword]
+#       >    ...
+#
+#       A list of the topic type's keywords.  Each line after the heading is the keyword and optionally its plural form.  This continues
+#       until the next line in "keyword: value" format.  "Add" isn't required.
+#
+#       - Keywords can only have letters and numbers.  No punctuation or spaces are allowed.
+#       - Keywords are not case sensitive.
+#       - Subsequent keyword sections add to the list.  They don't replace it.
+#       - Keywords can be redefined by other keyword sections.
+#
+#
+#   Revisions:
+#
+#       1.3:
+#
+#           The initial version of this file.
+#
+
+
+###############################################################################
+# Group: File Functions
+
+
+#
+#   Function: Load
+#
+#   Loads both the master and the project version of <Topics.txt>.
+#
+sub Load
+    {
+    my $self = shift;
+
+    # Add the special General topic type.
+
+    $types{::TOPIC_GENERAL()} = NaturalDocs::Topics::Type->New('General', 'General', 1, ::SCOPE_NORMAL(), undef);
+    $names{'general'} = ::TOPIC_GENERAL();
+    $indexable{::TOPIC_GENERAL()} = 1;
+    # There are no keywords for the general topic.
+
+
+    $self->LoadFile(1);  # Main
+
+    # Dependency: All the default topic types must be checked for existence.
+
+    # Check to see if the required types are defined.
+    foreach my $name (@requiredTypeNames)
+        {
+        if (!exists $names{lc($name)})
+            {  NaturalDocs::ConfigFile->AddError('The ' . $name . ' topic type must be defined in the main topics file.');  };
+        };
+
+    my $errorCount = NaturalDocs::ConfigFile->ErrorCount();
+
+    if ($errorCount)
+        {
+        NaturalDocs::ConfigFile->PrintErrorsAndAnnotateFile();
+        NaturalDocs::Error->SoftDeath('There ' . ($errorCount == 1 ? 'is an error' : 'are ' . $errorCount . ' errors')
+                                                    . ' in ' . NaturalDocs::Project->MainConfigFile('Topics.txt'));
+        }
+
+
+    $self->LoadFile();  # User
+
+    $errorCount = NaturalDocs::ConfigFile->ErrorCount();
+
+    if ($errorCount)
+        {
+        NaturalDocs::ConfigFile->PrintErrorsAndAnnotateFile();
+        NaturalDocs::Error->SoftDeath('There ' . ($errorCount == 1 ? 'is an error' : 'are ' . $errorCount . ' errors')
+                                                    . ' in ' . NaturalDocs::Project->UserConfigFile('Topics.txt'));
+        }
+    };
+
+
+#
+#   Function: LoadFile
+#
+#   Loads a particular version of <Topics.txt>.
+#
+#   Parameters:
+#
+#       isMain - Whether the file is the main file or not.
+#
+sub LoadFile #(isMain)
+    {
+    my ($self, $isMain) = @_;
+
+    my ($file, $status);
+
+    if ($isMain)
+        {
+        $file = NaturalDocs::Project->MainConfigFile('Topics.txt');
+        $status = NaturalDocs::Project->MainConfigFileStatus('Topics.txt');
+        }
+    else
+        {
+        $file = NaturalDocs::Project->UserConfigFile('Topics.txt');
+        $status = NaturalDocs::Project->UserConfigFileStatus('Topics.txt');
+        };
+
+    my $version;
+
+    if ($version = NaturalDocs::ConfigFile->Open($file))
+        {
+        # The format hasn't changed since the file was introduced.
+
+        if ($status == ::FILE_CHANGED())
+            {  NaturalDocs::Project->ReparseEverything();  };
+
+        my ($topicTypeKeyword, $topicTypeName, $topicType, $topicTypeObject, $inKeywords, $inIgnoredKeywords);
+
+        # Keys are topic type objects, values are unparsed strings.
+        my %canGroupWith;
+        tie %canGroupWith, 'Tie::RefHash';
+
+        while (my ($keyword, $value) = NaturalDocs::ConfigFile->GetLine())
+            {
+            if ($keyword)
+                {
+                $inKeywords = 0;
+                $inIgnoredKeywords = 0;
+                };
+
+            if ($keyword eq 'topic type')
+                {
+                $topicTypeKeyword = $keyword;
+                $topicTypeName = $value;
+
+                # Resolve conflicts and create the type if necessary.
+
+                $topicType = $self->MakeTopicType($topicTypeName);
+                my $lcTopicTypeName = lc($topicTypeName);
+
+                my $lcTopicTypeAName = $lcTopicTypeName;
+                $lcTopicTypeAName =~ tr/a-z0-9//cd;
+
+                if (!NaturalDocs::ConfigFile->HasOnlyCFChars($topicTypeName))
+                    {
+                    NaturalDocs::ConfigFile->AddError('Topic names can only have ' . NaturalDocs::ConfigFile->CFCharNames() . '.');
+                    }
+                elsif ($topicType eq ::TOPIC_GENERAL())
+                    {
+                    NaturalDocs::ConfigFile->AddError('You cannot define a General topic type.');
+                    }
+                elsif (defined $types{$topicType} || defined $names{$lcTopicTypeName} || defined $names{$lcTopicTypeAName})
+                    {
+                    NaturalDocs::ConfigFile->AddError('Topic type ' . $topicTypeName . ' is already defined or its name is too '
+                                                                     . 'similar to an existing name.  Use Alter Topic Type if you meant to override '
+                                                                     . 'its settings.');
+                    }
+                else
+                    {
+                    $topicTypeObject = NaturalDocs::Topics::Type->New($topicTypeName, $topicTypeName, 1, ::SCOPE_NORMAL(),
+                                                                                                  0, 0);
+
+                    $types{$topicType} = $topicTypeObject;
+                    $names{$lcTopicTypeName} = $topicType;
+                    $names{$lcTopicTypeAName} = $topicType;
+
+                    $indexable{$topicType} = 1;
+
+                    if ($isMain)
+                        {  push @mainTopicNames, $topicTypeName;  };
+                    };
+                }
+
+            elsif ($keyword eq 'alter topic type')
+                {
+                $topicTypeKeyword = $keyword;
+                $topicTypeName = $value;
+
+                # Resolve conflicts and create the type if necessary.
+
+                $topicType = $names{lc($topicTypeName)};
+
+                if (!defined $topicType)
+                    {  NaturalDocs::ConfigFile->AddError('Topic type ' . $topicTypeName . ' doesn\'t exist.');  }
+                elsif ($topicType eq ::TOPIC_GENERAL())
+                    {  NaturalDocs::ConfigFile->AddError('You cannot alter the General topic type.');  }
+                else
+                    {
+                    $topicTypeObject = $types{$topicType};
+                    };
+                }
+
+            elsif ($keyword =~ /^ignored? keywords?$/)
+                {
+                $inIgnoredKeywords = 1;
+
+                my @ignoredKeywords = split(/ ?, ?/, lc($value));
+
+                foreach my $ignoredKeyword (@ignoredKeywords)
+                    {
+                    delete $keywords{$ignoredKeyword};
+                    delete $pluralKeywords{$ignoredKeyword};
+                    };
+                }
+
+            # We continue even if there are errors in the topic type line so that we can find any other errors in the file as well.  We'd
+            # rather them all show up at once instead of them showing up one at a time between Natural Docs runs.  So we just ignore
+            # the settings if $topicTypeObject is undef.
+
+
+            elsif ($keyword eq 'plural')
+                {
+                my $pluralName = $value;
+                my $lcPluralName = lc($pluralName);
+
+                my $lcPluralAName = $lcPluralName;
+                $lcPluralAName =~ tr/a-zA-Z0-9//cd;
+
+                if (!NaturalDocs::ConfigFile->HasOnlyCFChars($pluralName))
+                    {
+                    NaturalDocs::ConfigFile->AddError('Plural names can only have '
+                                                                     . NaturalDocs::ConfigFile->CFCharNames() . '.');
+                    }
+                elsif ($lcPluralAName eq 'general')
+                    {
+                    NaturalDocs::ConfigFile->AddError('You cannot use General as a plural name for ' . $topicTypeName . '.');
+                    }
+                elsif ( (defined $names{$lcPluralName} && $names{$lcPluralName} ne $topicType) ||
+                         (defined $names{$lcPluralAName} && $names{$lcPluralAName} ne $topicType) )
+                    {
+                    NaturalDocs::ConfigFile->AddError($topicTypeName . "'s plural name, " . $pluralName
+                                                                     . ', is already defined or is too similar to an existing name.');
+                    }
+
+                elsif (defined $topicTypeObject)
+                    {
+                    $topicTypeObject->SetPluralName($pluralName);
+
+                    $names{$lcPluralName} = $topicType;
+                    $names{$lcPluralAName} = $topicType;
+                    };
+                }
+
+            elsif ($keyword eq 'index')
+                {
+                $value = lc($value);
+
+                if ($value eq 'yes')
+                    {
+                    if (defined $topicTypeObject)
+                        {
+                        $topicTypeObject->SetIndex(1);
+                        $indexable{$topicType} = 1;
+                        };
+                    }
+                elsif ($value eq 'no')
+                    {
+                    if (defined $topicTypeObject)
+                        {
+                        $topicTypeObject->SetIndex(0);
+                        delete $indexable{$topicType};
+                        };
+                    }
+                else
+                    {
+                    NaturalDocs::ConfigFile->AddError('Index lines can only be "yes" or "no".');
+                    };
+                }
+
+            elsif ($keyword eq 'class hierarchy')
+                {
+                $value = lc($value);
+
+                if ($value eq 'yes')
+                    {
+                    if (defined $topicTypeObject)
+                        {  $topicTypeObject->SetClassHierarchy(1);  };
+                    }
+                elsif ($value eq 'no')
+                    {
+                    if (defined $topicTypeObject)
+                        {  $topicTypeObject->SetClassHierarchy(0);  };
+                    }
+                else
+                    {
+                    NaturalDocs::ConfigFile->AddError('Class Hierarchy lines can only be "yes" or "no".');
+                    };
+                }
+
+            elsif ($keyword eq 'scope')
+                {
+                $value = lc($value);
+
+                if ($value eq 'normal')
+                    {
+                    if (defined $topicTypeObject)
+                        {  $topicTypeObject->SetScope(::SCOPE_NORMAL());  };
+                    }
+                elsif ($value eq 'start')
+                    {
+                    if (defined $topicTypeObject)
+                        {  $topicTypeObject->SetScope(::SCOPE_START());  };
+                    }
+                elsif ($value eq 'end')
+                    {
+                    if (defined $topicTypeObject)
+                        {  $topicTypeObject->SetScope(::SCOPE_END());  };
+                    }
+                elsif ($value eq 'always global')
+                    {
+                    if (defined $topicTypeObject)
+                        {  $topicTypeObject->SetScope(::SCOPE_ALWAYS_GLOBAL());  };
+                    }
+                else
+                    {
+                    NaturalDocs::ConfigFile->AddError('Scope lines can only be "normal", "start", "end", or "always global".');
+                    };
+                }
+
+            elsif ($keyword eq 'page title if first')
+                {
+                $value = lc($value);
+
+                if ($value eq 'yes')
+                    {
+                    if (defined $topicTypeObject)
+                        {  $topicTypeObject->SetPageTitleIfFirst(1);  };
+                    }
+                elsif ($value eq 'no')
+                    {
+                    if (defined $topicTypeObject)
+                        {  $topicTypeObject->SetPageTitleIfFirst(undef);  };
+                    }
+                else
+                    {
+                    NaturalDocs::ConfigFile->AddError('Page Title if First lines can only be "yes" or "no".');
+                    };
+                }
+
+            elsif ($keyword eq 'break lists')
+                {
+                $value = lc($value);
+
+                if ($value eq 'yes')
+                    {
+                    if (defined $topicTypeObject)
+                        {  $topicTypeObject->SetBreakLists(1);  };
+                    }
+                elsif ($value eq 'no')
+                    {
+                    if (defined $topicTypeObject)
+                        {  $topicTypeObject->SetBreakLists(undef);  };
+                    }
+                else
+                    {
+                    NaturalDocs::ConfigFile->AddError('Break Lists lines can only be "yes" or "no".');
+                    };
+                }
+
+            elsif ($keyword eq 'can group with')
+                {
+                if (defined $topicTypeObject)
+                    {  $canGroupWith{$topicTypeObject} = lc($value);  };
+                }
+
+            elsif ($keyword =~ /^(?:add )?keywords?$/)
+                {
+                $inKeywords = 1;
+                }
+
+            elsif (defined $keyword)
+                {  NaturalDocs::ConfigFile->AddError($keyword . ' is not a valid keyword.');  }
+
+            elsif (!$inKeywords && !$inIgnoredKeywords)
+                {
+                NaturalDocs::ConfigFile->AddError('All lines in ' . $topicTypeKeyword . ' sections must begin with a keyword.');
+                }
+
+            else # No keyword but in keyword section.
+                {
+                $value = lc($value);
+
+                if ($value =~ /^([a-z0-9 ]*[a-z0-9]) ?, ?([a-z0-9 ]+)$/)
+                    {
+                    my ($singular, $plural) = ($1, $2);
+
+                    if ($inIgnoredKeywords)
+                        {
+                        delete $keywords{$singular};
+                        delete $keywords{$plural};
+                        delete $pluralKeywords{$singular};
+                        delete $pluralKeywords{$plural};
+                        }
+                    elsif (defined $topicTypeObject)
+                        {
+                        $keywords{$singular} = $topicType;
+                        delete $pluralKeywords{$singular};
+
+                        $pluralKeywords{$plural} = $topicType;
+                        delete $keywords{$plural};
+                        };
+                    }
+                elsif ($value =~ /^[a-z0-9 ]+$/)
+                    {
+                    if ($inIgnoredKeywords)
+                        {
+                        delete $keywords{$value};
+                        delete $pluralKeywords{$value};
+                        }
+                    elsif (defined $topicType)
+                        {
+                        $keywords{$value} = $topicType;
+                        delete $pluralKeywords{$value};
+                        };
+                    }
+                else
+                    {
+                    NaturalDocs::ConfigFile->AddError('Keywords can only have letters, numbers, and spaces.  '
+                                                                     . 'Plurals must be separated by a comma.');
+                    };
+                };
+            };
+
+        NaturalDocs::ConfigFile->Close();
+
+
+        # Parse out the Can Group With lines now that everything's defined.
+
+        while (my ($typeObject, $value) = each %canGroupWith)
+            {
+            my @values = split(/ ?, ?/, $value);
+            my @types;
+
+            foreach my $value (@values)
+                {
+                # We're just going to ignore invalid items.
+                if (exists $names{$value})
+                    {  push @types, $names{$value};  };
+                };
+
+            if (scalar @types)
+                {  $typeObject->SetCanGroupWith(\@types);  };
+            };
+        }
+
+    else # couldn't open file
+        {
+        if ($isMain)
+            {  die "Couldn't open topics file " . $file . "\n";  }
+        else
+            {  NaturalDocs::Project->ReparseEverything();  };
+        };
+    };
+
+
+#
+#   Function: Save
+#
+#   Saves the main and user versions of <Topics.txt>.
+#
+sub Save
+    {
+    my $self = shift;
+
+    $self->SaveFile(1); # Main
+    $self->SaveFile(0); # User
+    };
+
+
+#
+#   Function: SaveFile
+#
+#   Saves a particular version of <Topics.txt>.
+#
+#   Parameters:
+#
+#       isMain - Whether the file is the main file or not.
+#
+sub SaveFile #(isMain)
+    {
+    my ($self, $isMain) = @_;
+
+    my $file;
+
+    if ($isMain)
+        {
+        if (NaturalDocs::Project->MainConfigFileStatus('Topics.txt') == ::FILE_SAME())
+            {  return;  };
+        $file = NaturalDocs::Project->MainConfigFile('Topics.txt');
+        }
+    else
+        {
+        # We have to check the main one two because this lists the topics defined in it.
+        if (NaturalDocs::Project->UserConfigFileStatus('Topics.txt') == ::FILE_SAME() &&
+            NaturalDocs::Project->MainConfigFileStatus('Topics.txt') == ::FILE_SAME())
+            {  return;  };
+        $file = NaturalDocs::Project->UserConfigFile('Topics.txt');
+        };
+
+
+    # Array of topic type names in the order they appear in the file.  If Alter Topic Type is used, the name will end with an asterisk.
+    my @topicTypeOrder;
+
+    # Keys are topic type names, values are property hashrefs.  Hashref keys are the property names, values the value.
+    # For keywords, the key is Keywords and the values are arrayrefs of singular and plural pairs.  If no plural is defined, the entry
+    # will be undef.
+    my %properties;
+
+    # List of ignored keywords specified as Ignore Keywords: [keyword], [keyword], ...
+    my @inlineIgnoredKeywords;
+
+    # List of ignored keywords specified in [keyword], [plural keyword] lines.  Done in pairs, like for regular keywords.
+    my @separateIgnoredKeywords;
+
+    my $inIgnoredKeywords;
+
+    if (NaturalDocs::ConfigFile->Open($file))
+        {
+        # We can assume the file is valid.
+
+        my ($keyword, $value, $topicTypeName);
+
+        while (($keyword, $value) = NaturalDocs::ConfigFile->GetLine())
+            {
+            $keyword = lc($keyword);
+
+            if ($keyword eq 'topic type' || $keyword eq 'alter topic type')
+                {
+                $topicTypeName = $types{ $names{lc($value)} }->Name();
+
+                if ($keyword eq 'alter topic type')
+                    {  $topicTypeName .= '*';  };
+
+                push @topicTypeOrder, $topicTypeName;
+
+                if (!exists $properties{$topicTypeName})
+                    {  $properties{$topicTypeName} = { 'keywords' => [ ] };  };
+                }
+
+            elsif ($keyword eq 'plural')
+                {
+                $properties{$topicTypeName}->{$keyword} = $value;
+                }
+
+            elsif ($keyword eq 'index' ||
+                    $keyword eq 'scope' ||
+                    $keyword eq 'page title if first' ||
+                    $keyword eq 'class hierarchy' ||
+                    $keyword eq 'break lists' ||
+                    $keyword eq 'can group with')
+                {
+                $properties{$topicTypeName}->{$keyword} = lc($value);
+                }
+
+            elsif ($keyword =~ /^(?:add )?keywords?$/)
+                {
+                $inIgnoredKeywords = 0;
+                }
+
+            elsif ($keyword =~ /^ignored? keywords?$/)
+                {
+                $inIgnoredKeywords = 1;
+                if ($value)
+                    {  push @inlineIgnoredKeywords, split(/ ?, ?/, $value);  };
+                }
+
+            elsif (!$keyword)
+                {
+                my ($singular, $plural) = split(/ ?, ?/, lc($value));
+
+                if ($inIgnoredKeywords)
+                    {  push @separateIgnoredKeywords, $singular, $plural;  }
+                else
+                    {  push @{$properties{$topicTypeName}->{'keywords'}}, $singular, $plural;  };
+                };
+            };
+
+        NaturalDocs::ConfigFile->Close();
+        };
+
+
+    if (!open(FH_TOPICS, '>' . $file))
+        {
+        # The main file may be on a shared volume or some other place the user doesn't have write access to.  Since this is only to
+        # reformat the file, we can ignore the failure.
+        if ($isMain)
+            {  return;  }
+        else
+            {  die "Couldn't save " . $file;  };
+        };
+
+    print FH_TOPICS 'Format: ' . NaturalDocs::Settings->TextAppVersion() . "\n\n";
+
+    # Remember the 80 character limit.
+
+    if ($isMain)
+        {
+        print FH_TOPICS
+        "# This is the main Natural Docs topics file.  If you change anything here, it\n"
+        . "# will apply to EVERY PROJECT you use Natural Docs on.  If you'd like to\n"
+        . "# change something for just one project, edit the Topics.txt in its project\n"
+        . "# directory instead.\n";
+        }
+    else
+        {
+        print FH_TOPICS
+        "# This is the Natural Docs topics file for this project.  If you change anything\n"
+        . "# here, it will apply to THIS PROJECT ONLY.  If you'd like to change something\n"
+        . "# for all your projects, edit the Topics.txt in Natural Docs' Config directory\n"
+        . "# instead.\n\n\n";
+
+        if (scalar @inlineIgnoredKeywords || scalar @separateIgnoredKeywords)
+            {
+            if (scalar @inlineIgnoredKeywords == 1 && !scalar @separateIgnoredKeywords)
+                {
+                print FH_TOPICS 'Ignore Keyword: ' . $inlineIgnoredKeywords[0] . "\n";
+                }
+            else
+                {
+                print FH_TOPICS
+                'Ignore Keywords: ' . join(', ', @inlineIgnoredKeywords) . "\n";
+
+                for (my $i = 0; $i < scalar @separateIgnoredKeywords; $i += 2)
+                    {
+                    print FH_TOPICS '   ' . $separateIgnoredKeywords[$i];
+
+                    if (defined $separateIgnoredKeywords[$i + 1])
+                        {  print FH_TOPICS ', ' . $separateIgnoredKeywords[$i + 1];  };
+
+                    print FH_TOPICS "\n";
+                    };
+                };
+            }
+        else
+            {
+            print FH_TOPICS
+            "# If you'd like to prevent keywords from being recognized by Natural Docs, you\n"
+            . "# can do it like this:\n"
+            . "# Ignore Keywords: [keyword], [keyword], ...\n"
+            . "#\n"
+            . "# Or you can use the list syntax like how they are defined:\n"
+            . "# Ignore Keywords:\n"
+            . "#    [keyword]\n"
+            . "#    [keyword], [plural keyword]\n"
+            . "#    ...\n";
+            };
+        };
+
+    print FH_TOPICS # [CFChars]
+    "\n\n"
+    . "#-------------------------------------------------------------------------------\n"
+    . "# SYNTAX:\n"
+    . "#\n";
+
+    if ($isMain)
+        {
+        print FH_TOPICS
+        "# Topic Type: [name]\n"
+        . "#    Creates a new topic type.  Each type gets its own index and behavior\n"
+        . "#    settings.  Its name can have letters, numbers, spaces, and these\n"
+        . "#    charaters: - / . '\n"
+        . "#\n"
+        . "#    The Enumeration type is special.  It's indexed with Types but its members\n"
+        . "#    are indexed with Constants according to the rules in Languages.txt.\n"
+        . "#\n"
+        }
+    else
+        {
+        print FH_TOPICS
+        "# Topic Type: [name]\n"
+        . "# Alter Topic Type: [name]\n"
+        . "#    Creates a new topic type or alters one from the main file.  Each type gets\n"
+        . "#    its own index and behavior settings.  Its name can have letters, numbers,\n"
+        . "#    spaces, and these charaters: - / . '\n"
+        . "#\n";
+        };
+
+    print FH_TOPICS
+    "# Plural: [name]\n"
+    . "#    Sets the plural name of the topic type, if different.\n"
+    . "#\n"
+    . "# Keywords:\n"
+    . "#    [keyword]\n"
+    . "#    [keyword], [plural keyword]\n"
+    . "#    ...\n";
+
+    if ($isMain)
+        {
+        print FH_TOPICS
+        "#    Defines a list of keywords for the topic type.  They may only contain\n"
+        . "#    letters, numbers, and spaces and are not case sensitive.  Plural keywords\n"
+        . "#    are used for list topics.\n";
+        }
+    else
+        {
+        print FH_TOPICS
+        "#    Defines or adds to the list of keywords for the topic type.  They may only\n"
+        . "#    contain letters, numbers, and spaces and are not case sensitive.  Plural\n"
+        . "#    keywords are used for list topics.  You can redefine keywords found in the\n"
+        . "#    main topics file.\n";
+        }
+
+    print FH_TOPICS
+    "#\n"
+    . "# Index: [yes|no]\n"
+    . "#    Whether the topics get their own index.  Defaults to yes.  Everything is\n"
+    . "#    included in the general index regardless of this setting.\n"
+    . "#\n"
+    . "# Scope: [normal|start|end|always global]\n"
+    . "#    How the topics affects scope.  Defaults to normal.\n"
+    . "#    normal        - Topics stay within the current scope.\n"
+    . "#    start         - Topics start a new scope for all the topics beneath it,\n"
+    . "#                    like class topics.\n"
+    . "#    end           - Topics reset the scope back to global for all the topics\n"
+    . "#                    beneath it.\n"
+    . "#    always global - Topics are defined as global, but do not change the scope\n"
+    . "#                    for any other topics.\n"
+    . "#\n"
+    . "# Class Hierarchy: [yes|no]\n"
+    . "#    Whether the topics are part of the class hierarchy.  Defaults to no.\n"
+    . "#\n"
+    . "# Page Title If First: [yes|no]\n"
+    . "#    Whether the topic's title becomes the page title if it's the first one in\n"
+    . "#    a file.  Defaults to no.\n"
+    . "#\n"
+    . "# Break Lists: [yes|no]\n"
+    . "#    Whether list topics should be broken into individual topics in the output.\n"
+    . "#    Defaults to no.\n"
+    . "#\n"
+    . "# Can Group With: [type], [type], ...\n"
+    . "#    Defines a list of topic types that this one can possibly be grouped with.\n"
+    . "#    Defaults to none.\n"
+    . "#-------------------------------------------------------------------------------\n\n";
+
+    my $listToPrint;
+
+    if ($isMain)
+        {
+        print FH_TOPICS
+        "# The following topics MUST be defined in this file:\n"
+        . "#\n";
+        $listToPrint = \@requiredTypeNames;
+        }
+    else
+        {
+        print FH_TOPICS
+        "# The following topics are defined in the main file, if you'd like to alter\n"
+        . "# their behavior or add keywords:\n"
+        . "#\n";
+        $listToPrint = \@mainTopicNames;
+        }
+
+    print FH_TOPICS
+    Text::Wrap::wrap('#    ', '#    ', join(', ', @$listToPrint)) . "\n"
+    . "\n"
+    . "# If you add something that you think would be useful to other developers\n"
+    . "# and should be included in Natural Docs by default, please e-mail it to\n"
+    . "# topics [at] naturaldocs [dot] org.\n";
+
+    # Existence hash.  We do this because we want the required ones to go first by adding them to @topicTypeOrder, but we don't
+    # want them to appear twice.
+    my %doneTopicTypes;
+    my ($altering, $numberOfProperties);
+
+    if ($isMain)
+        {  unshift @topicTypeOrder, @requiredTypeNames;  };
+
+    my @propertyOrder = ('Plural', 'Index', 'Scope', 'Class Hierarchy', 'Page Title If First', 'Break Lists');
+
+    foreach my $topicType (@topicTypeOrder)
+        {
+        if (!exists $doneTopicTypes{$topicType})
+            {
+            if (substr($topicType, -1) eq '*')
+                {
+                print FH_TOPICS "\n\n"
+                . 'Alter Topic Type: ' . substr($topicType, 0, -1) . "\n\n";
+
+                $altering = 1;
+                $numberOfProperties = 0;
+                }
+            else
+                {
+                print FH_TOPICS "\n\n"
+                . 'Topic Type: ' . $topicType . "\n\n";
+
+                $altering = 0;
+                $numberOfProperties = 0;
+                };
+
+            foreach my $property (@propertyOrder)
+                {
+                if (exists $properties{$topicType}->{lc($property)})
+                    {
+                    print FH_TOPICS
+                    '   ' . $property . ': ' . ucfirst( $properties{$topicType}->{lc($property)} ) . "\n";
+
+                    $numberOfProperties++;
+                    };
+                };
+
+            if (exists $properties{$topicType}->{'can group with'})
+                {
+                my @typeStrings = split(/ ?, ?/, lc($properties{$topicType}->{'can group with'}));
+                my @types;
+
+                foreach my $typeString (@typeStrings)
+                    {
+                    if (exists $names{$typeString})
+                        {  push @types, $names{$typeString};  };
+                    };
+
+                if (scalar @types)
+                    {
+                    for (my $i = 0; $i < scalar @types; $i++)
+                        {
+                        my $name = NaturalDocs::Topics->NameOfType($types[$i], 1);
+
+                        if ($i == 0)
+                            {  print FH_TOPICS '   Can Group With: ' . $name;  }
+                        else
+                            {  print FH_TOPICS ', ' . $name;  };
+                        };
+
+                    print FH_TOPICS "\n";
+                    $numberOfProperties++;
+                    };
+                };
+
+            if (scalar @{$properties{$topicType}->{'keywords'}})
+                {
+                if ($numberOfProperties > 1)
+                    {  print FH_TOPICS "\n";  };
+
+                print FH_TOPICS
+                '   ' . ($altering ? 'Add ' : '') . 'Keywords:' . "\n";
+
+                my $keywords = $properties{$topicType}->{'keywords'};
+
+                for (my $i = 0; $i < scalar @$keywords; $i += 2)
+                    {
+                    print FH_TOPICS '      ' . $keywords->[$i];
+
+                    if (defined $keywords->[$i + 1])
+                        {  print FH_TOPICS ', ' . $keywords->[$i + 1];  };
+
+                    print FH_TOPICS "\n";
+                    };
+                };
+
+            $doneTopicTypes{$topicType} = 1;
+            };
+        };
+
+    close(FH_TOPICS);
+    };
+
+
+
+###############################################################################
+# Group: Functions
+
+
+#
+#   Function: KeywordInfo
+#
+#   Returns information about a topic keyword.
+#
+#   Parameters:
+#
+#       keyword - The keyword, which may be plural.
+#
+#   Returns:
+#
+#       The array ( topicType, info, isPlural ), or an empty array if the keyword doesn't exist.
+#
+#       topicType - The <TopicType> of the keyword.
+#       info - The <NaturalDocs::Topics::Type> of its type.
+#       isPlural - Whether the keyword was plural or not.
+#
+sub KeywordInfo #(keyword)
+    {
+    my ($self, $keyword) = @_;
+
+    $keyword = lc($keyword);
+
+    my $type = $keywords{$keyword};
+
+    if (defined $type)
+        {  return ( $type, $types{$type}, undef );  };
+
+    $type = $pluralKeywords{$keyword};
+
+    if (defined $type)
+        {  return ( $type, $types{$type}, 1 );  };
+
+    return ( );
+    };
+
+
+#
+#   Function: NameInfo
+#
+#   Returns information about a topic name.
+#
+#   Parameters:
+#
+#      name - The topic type name, which can be plural and/or alphanumeric only.
+#
+#   Returns:
+#
+#       The array ( topicType, info ), or an empty array if the name doesn't exist.  Note that unlike <KeywordInfo()>, this
+#       does *not* tell you whether the name is plural or not.
+#
+#       topicType - The <TopicType> of the name.
+#       info - The <NaturalDocs::Topics::Type> of the type.
+#
+sub NameInfo #(name)
+    {
+    my ($self, $name) = @_;
+
+    my $type = $names{lc($name)};
+
+    if (defined $type)
+        {  return ( $type, $types{$type} );  }
+    else
+        {  return ( );  };
+    };
+
+
+#
+#   Function: TypeInfo
+#
+#   Returns information about a <TopicType>.
+#
+#   Parameters:
+#
+#      type - The <TopicType>.
+#
+#   Returns:
+#
+#       The <NaturalDocs::Topics::Type> of the type, or undef if it didn't exist.
+#
+sub TypeInfo #(type)
+    {
+    my ($self, $type) = @_;
+    return $types{$type};
+    };
+
+
+#
+#   Function: NameOfType
+#
+#   Returns the name of the passed <TopicType>, or undef if it doesn't exist.
+#
+#   Parameters:
+#
+#       topicType - The <TopicType>.
+#       plural - Whether to return the plural instead of the singular.
+#       alphanumericOnly - Whether to strips everything but alphanumeric characters out.  Case isn't modified.
+#
+#   Returns:
+#
+#       The topic type name, according to what was specified in the parameters, or undef if it doesn't exist.
+#
+sub NameOfType #(topicType, plural, alphanumericOnly)
+    {
+    my ($self, $topicType, $plural, $alphanumericOnly) = @_;
+
+    my $topicObject = $types{$topicType};
+
+    if (!defined $topicObject)
+        {  return undef;  };
+
+    my $topicName = ($plural ? $topicObject->PluralName() : $topicObject->Name());
+
+    if ($alphanumericOnly)
+        {  $topicName =~ tr/a-zA-Z0-9//cd;  };
+
+    return $topicName;
+    };
+
+
+#
+#   Function: TypeFromName
+#
+#   Returns a <TopicType> for the passed topic name.
+#
+#   Parameters:
+#
+#       topicName - The name of the topic, which can be plural and/or alphanumeric only.
+#
+#   Returns:
+#
+#       The <TopicType>.  It does not specify whether the name was plural or not.
+#
+sub TypeFromName #(topicName)
+    {
+    my ($self, $topicName) = @_;
+
+    return $names{lc($topicName)};
+    };
+
+
+#
+#   Function: IsValidType
+#
+#   Returns whether the passed <TopicType> is defined.
+#
+sub IsValidType #(type)
+    {
+    my ($self, $type) = @_;
+    return exists $types{$type};
+    };
+
+
+#
+#   Function: TypeFromLegacy
+#
+#   Returns a <TopicType> for the passed legacy topic type integer.  <TopicTypes> were changed from integer constants to
+#   strings in 1.3.
+#
+sub TypeFromLegacy #(legacyInt)
+    {
+    my ($self, $int) = @_;
+    return $legacyTypes[$int];
+    };
+
+
+#
+#   Function: AllIndexableTypes
+#
+#   Returns an array of all possible indexable <TopicTypes>.
+#
+sub AllIndexableTypes
+    {
+    my ($self) = @_;
+    return keys %indexable;
+    };
+
+
+
+###############################################################################
+# Group: Support Functions
+
+
+#
+#   Function: MakeTopicType
+#
+#   Returns a <TopicType> for the passed topic name.  It does not check to see if it exists already.
+#
+#   Parameters:
+#
+sub MakeTopicType #(topicName)
+    {
+    my ($self, $topicName) = @_;
+
+    # Dependency: The values of the default topic type constants must match what is generated here.
+
+    # Turn everything to lowercase and strip non-alphanumeric characters.
+    $topicName = lc($topicName);
+    $topicName =~ tr/a-z0-9//cd;
+
+    return $topicName;
+    };
+
+
+
+1;

Added: trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Version.pm
===================================================================
--- trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Version.pm	                        (rev 0)
+++ trunk/build/NaturalDocs-1.4/Modules/NaturalDocs/Version.pm	2009-07-01 15:30:00 UTC (rev 4275)
@@ -0,0 +1,384 @@
+###############################################################################
+#
+#   Package: NaturalDocs::Version
+#
+###############################################################################
+#
+#   A package for handling version information.  What?  That's right.  Although it should be easy and obvious, version numbers
+#   need to be dealt with in a variety of formats, plus there's compatibility with older releases which handled it differently.  I
+#   wanted to centralize the code after it started getting complicated.  So there ya go.
+#
+###############################################################################
+
+# 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::Version;
+
+
+###############################################################################
+# Group: Functions
+
+
+#
+#   Function: ToString
+#
+#   Converts a <VersionInt> to a string.
+#
+sub ToString #(VersionInt version) => string
+    {
+    my ($self, $version) = @_;
+
+    my ($major, $minor, $month, $day, $year) = $self->ToValues($version);
+
+    if ($minor % 10 == 0)
+        {  $minor /= 10;  };
+
+    if ($day)
+        {  return sprintf('Development Release %02d-%02d-%d (%d.%d base)', $month, $day, $year, $major, $minor);  }
+    else
+        {  return $major . '.' . $minor;  };
+    };
+
+
+#
+#   Function: FromString
+#
+#   Converts a version string to a <VersionInt>.
+#
+sub FromString #(string string) => VersionInt
+    {
+    my ($self, $string) = @_;
+
+    if ($string eq '1')
+        {
+        return $self->FromValues(0, 91, 0, 0, 0);  # 0.91
+        }
+    else
+        {
+        my ($major, $minor, $month, $day, $year);
+
+        if ($string =~ /^(\d{1,2})\.(\d{1,2})$/)
+            {
+            ($major, $minor) = ($1, $2);
+            ($month, $day, $year) = (0, 0, 0);
+            }
+        elsif ($string =~ /^Development Release (\d{1,2})-(\d{1,2})-(\d\d\d\d) \((\d{1,2})\.(\d{1,2}) base\)$/)
+            {
+            ($month, $day, $year, $major, $minor) = ($1, $2, $3, $4, $5);
+
+            # We have to do sanity checking because these can come from user-editable text files.  The version numbers should
+            # already be constrained simply by being forced to have only two digits.
+
+            if ($month > 12 || $month < 1 || $day > 31 || $day < 1 || $year > 2255 || $year < 2000)
+                {  die 'The version string ' . $string . " doesn't have a valid date.\n";  };
+            }
+        else
+            {
+            die 'The version string ' . $string . " isn't in a recognized format.\n";
+            };
+
+        if (length $minor == 1)
+            {  $minor *= 10;  };
+
+        return $self->FromValues($major, $minor, $month, $day, $year);
+        };
+    };
+
+
+#
+#   Function: ToTextFile
+#
+#   Writes a <VersionInt> to a text file.
+#
+#   Parameters:
+#
+#       fileHandle - The handle of the file to write it to.  It should be at the correct location.
+#       version - The <VersionInt> to write.
+#
+sub ToTextFile #(handle fileHandle, VersionInt version)
+    {
+    my ($self, $fileHandle, $version) = @_;
+
+    print $fileHandle $self->ToString($version) . "\n";
+    };
+
+
+#
+#   Function: FromTextFile
+#
+#   Retrieves a <VersionInt> from a text file.
+#
+#   Parameters:
+#
+#       fileHandle - The handle of the file to read it from.  It should be at the correct location.
+#
+#   Returns:
+#
+#       The <VersionInt>.
+#
+sub FromTextFile #(handle fileHandle) => VersionInt
+    {
+    my ($self, $fileHandle) = @_;
+
+    my $version = <$fileHandle>;
+    ::XChomp(\$version);
+
+    return $self->FromString($version);
+    };
+
+
+#
+#   Function: ToBinaryFile
+#
+#   Writes a <VersionInt> to a binary file.
+#
+#   Parameters:
+#
+#       fileHandle - The handle of the file to write it to.  It should be at the correct location.
+#       version - The <VersionInt> to write.
+#
+sub ToBinaryFile #(handle fileHandle, VersionInt version)
+    {
+    my ($self, $fileHandle, $version) = @_;
+
+    my ($major, $minor, $month, $day, $year) = $self->ToValues($version);
+
+    # 1.35 development releases are encoded as 1.36.  Everything else is literal.
+    if ($day && $major == 1 && $minor == 35)
+        {  $minor = 36;  };
+
+    print $fileHandle pack('CC', $major, $minor);
+
+    # Date fields didn't exist with 1.35 stable and earlier.  1.35 development releases are encoded as 1.36, so this works.
+    if ($major > 1 || ($major == 1 && $minor > 35))
+        {
+        if ($day)
+            {  $year -= 2000;  };
+
+        print $fileHandle pack('CCC', $month, $day, $year);
+        };
+    };
+
+
+#
+#   Function: FromBinaryFile
+#
+#   Retrieves a <VersionInt> from a binary file.
+#
+#   Parameters:
+#
+#       fileHandle - The handle of the file to read it from.  It should be at the correct location.
+#
+#   Returns:
+#
+#       The <VersionInt>.
+#
+sub FromBinaryFile #(handle fileHandle) => VersionInt
+    {
+    my ($self, $fileHandle) = @_;
+
+    my ($major, $minor, $month, $day, $year);
+
+    my $raw;
+    read($fileHandle, $raw, 2);
+
+    ($major, $minor) = unpack('CC', $raw);
+
+    # 1.35 stable is the last release without the date fields.  1.35 development releases are encoded as 1.36, so this works.
+    if ($major > 1 || ($major == 1 && $minor > 35))
+        {
+        read($fileHandle, $raw, 3);
+        ($month, $day, $year) = unpack('CCC', $raw);
+
+        if ($day)
+            {  $year += 2000;  };
+        }
+    else
+        {  ($month, $day, $year) = (0, 0, 0);  };
+
+    # Fix the 1.35 development release special encoding.
+    if ($major == 1 && $minor == 36)
+        {  $minor = 35;  };
+
+
+    return $self->FromValues($major, $minor, $month, $day, $year);
+    };
+
+
+#
+#   Function: ToValues
+#
+#   Converts a <VersionInt> to the array ( major, minor, month, day, year ).  The minor version will be in two digit form, so x.2
+#   will return 20.  The date fields will be zero for stable releases.
+#
+sub ToValues #(VersionInt version) => ( int, int, int, int, int )
+    {
+    my ($self, $version) = @_;
+
+    my $major = ($version & 0x00003F80) >> 7;
+    my $minor = ($version & 0x0000007F);
+    my $month = ($version & 0x00780000) >> 19;
+    my $day = ($version & 0x0007C000) >> 14;
+    my $year = ($version & 0x7F800000) >> 23;
+
+    if ($year)
+        {  $year += 2000;  };
+
+    return ( $major, $minor, $month, $day, $year );
+    };
+
+
+#
+#   Function: FromValues
+#
+#   Returns a <VersionInt> created from the passed values.
+#
+#   Parameters:
+#
+#       major - The major version number.  For development releases, it should be the stable version it's based off of.
+#       minor - The minor version number.  It should always be two digits, so x.2 should pass 20.  For development
+#                  releases, it should be the stable version it's based off of.
+#       month - The numeric month of the development release.  For stable releases it should be zero.
+#       day - The day of the development release.  For stable releases it should be zero.
+#       year - The year of the development release.  For stable releases it should be zero.
+#
+#   Returns:
+#
+#       The <VersionInt>.
+#
+sub FromValues #(int major, int minor, int month, int day, int year) => VersionInt
+    {
+    my ($self, $major, $minor, $month, $day, $year) = @_;
+
+    if ($day)
+        {  $year -= 2000;  };
+
+    return ($major << 7) + ($minor) + ($month << 19) + ($day << 14) + ($year << 23);
+    };
+
+
+#
+#   Function: CheckFileFormat
+#
+#   Checks if a file's format is compatible with the current release.
+#
+#   - If the application is a development release or the file is from one, this only returns true if they are from the exact same
+#     development release.
+#   - If neither of them are development releases, this only returns true if the file is from a release between the minimum specified
+#     and the current version.  If there's no minimum it just checks that it's below the current version.
+#
+#   Parameters:
+#
+#       fileVersion - The <VersionInt> of the file format.
+#       minimumVersion - The minimum <VersionInt> required of the file format.  May be undef.
+#
+#   Returns:
+#
+#       Whether the file's format is compatible per the above rules.
+#
+sub CheckFileFormat #(VersionInt fileVersion, optional VersionInt minimumVersion) => bool
+    {
+    my ($self, $fileVersion, $minimumVersion) = @_;
+
+    my $appVersion = NaturalDocs::Settings->AppVersion();
+
+    if ($self->IsDevelopmentRelease($appVersion) || $self->IsDevelopmentRelease($fileVersion))
+        {  return ($appVersion == $fileVersion);  }
+    elsif ($minimumVersion && $fileVersion < $minimumVersion)
+        {  return 0;  }
+    else
+        {  return ($fileVersion <= $appVersion);  };
+    };
+
+
+#
+#   Function: IsDevelopmentRelease
+#
+#   Returns whether the passed <VersionInt> is for a development release.
+#
+sub IsDevelopmentRelease #(VersionInt version) => bool
+    {
+    my ($self, $version) = @_;
+
+    # Return if any of the date fields are set.
+    return ($version & 0x7FFFC000);
+    };
+
+
+
+###############################################################################
+# Group: Implementation
+
+#
+#   About: String Format
+#
+#   Full Releases:
+#
+#       Full releases are in the common major.minor format.  Either part can be up to two digits.  The minor version is interpreted
+#       as decimal places, so 1.3 > 1.22.  There are no leading or trailing zeroes.
+#
+#   Development Releases:
+#
+#       Development releases are in the format "Development Release mm-dd-yyyy (vv.vv base)" where vv.vv is the version
+#       number of the full release it's based off of.  The month and day will have leading zeroes where applicable.  Example:
+#       "Development Release 07-09-2006 (1.35 base)".
+#
+#   0.91 and Earlier:
+#
+#       Text files from releases prior to 0.95 had a separate file format version number that was used instead of the application
+#       version.  These were never changed between 0.85 and 0.91, so they are simply "1".  Text version numbers that are "1"
+#       instead of "1.0" will be interpreted as 0.91.
+#
+
+#
+#   About: Integer Format
+#
+#   <VersionInts> are 32-bit values with the bit distribution below.
+#
+#   > s yyyyyyyy mmmm ddddd vvvvvvv xxxxxxx
+#   > [syyy|yyyy] [ymmm|mddd] [ddvv|vvvv] [vxxx|xxxx]
+#
+#   s - The sign bit.  Always zero, so it's always interpreted as positive.
+#   y - The year bits if it's a development release, zero otherwise.  2000 is added to the value, so the range is from 2000 to 2255.
+#   m - The month bits if it's a development release, zero otherwise.
+#   d - The day bits if it's a development release, zero otherwise.
+#   v - The major version bits.  For development releases, it's the last stable version it was based off of.
+#   x - The minor version bits.  It's always stored as two decimals, so x.2 would store 20 here.  For development releases, it's the
+#        last stable version it was based off of.
+#
+#   It's stored with the development release date at a higher significance than the version because we want a stable release to
+#   always treat a development release as higher than itself, and thus not attempt to read any of the data files.  I'm not tracking
+#   data file formats at the development release level.
+#
+
+#
+#   About: Binary File Format
+#
+#   Current:
+#
+#       Five 8-bit unsigned values, appearing major, minor, month, day, year.  Minor is always stored with two digits, so x.2 would
+#       store 20.  Year is stored minus 2000, so 2006 is stored 6.  Stable releases store zero for all the date fields.
+#
+#   1.35 Development Releases:
+#
+#       1.35-based development releases are stored the same as current releases, but with 1.36 as the version number.  This is
+#       done so previous versions of Natural Docs that didn't include the date fields would still know it's a higher version.  There is
+#       no actual 1.36 release.
+#
+#   1.35 and Earlier:
+#
+#       Two 8-bit unsigned values, appearing major then minor.  Minor is always stored with two digits, so x.2 would store 20.
+#
+
+#
+#   About: Text File Format
+#
+#   In text files, versions are the <String Format> followed by a native line break.
+#
+
+
+1;

Added: trunk/build/NaturalDocs-1.4/NaturalDocs
===================================================================
--- trunk/build/NaturalDocs-1.4/NaturalDocs	                        (rev 0)
+++ trunk/build/NaturalDocs-1.4/NaturalDocs	2009-07-01 15:30:00 UTC (rev 4275)
@@ -0,0 +1,400 @@
+#!/usr/bin/perl
+
+=begin nd
+
+    Script: NaturalDocs
+    ___________________________________________________________________________
+
+    Version 1.4
+
+    Copyright (C) 2003-2008 Greg Valure
+
+    http://www.naturaldocs.org
+
+
+    About: License
+
+        Licensed under the GNU General Public License
+
+        This program is free software; you can redistribute it and/or modify
+        it under the terms of the GNU General Public License as published by
+        the Free Software Foundation; either version 2 of the License, or
+        (at your option) any later version.
+
+        This program is distributed in the hope that it will be useful,
+        but WITHOUT ANY WARRANTY; without even the implied warranty of
+        MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+        GNU General Public License for more details.
+
+        You should have received a copy of the GNU General Public License
+        along with this program; if not, visit http://www.gnu.org/licenses/gpl.txt
+        or write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+        Boston, MA  02111-1307  USA.
+
+
+    Topic: Code Conventions
+
+        - Every package function is called with an arrow operator.  It's needed for inheritance in some places, and consistency
+         when it's not.
+
+        - No constant will ever be zero or undef.  Those are reserved so any piece of code can allow a "none of the above" option
+         and not worry about conflicts with an existing value.
+
+        - Existence hashes are hashes where the value doesn't matter.  It acts more as a set, where the existence of the key is
+         the significant part.
+
+
+    Topic: File Format Conventions
+
+        - All integers appear in big-endian format.  So a UInt16 should be handled with a 'n' in pack and unpack, not with a 'S'.
+
+        - AString16's are a big-endian UInt16 followed by that many ASCII characters.  A null-terminator is not stored.
+
+        - If a higher-level type is described in a file format, that means the loading and saving format is handled by that package.
+         For example, if you see <SymbolString> in the format, that means <NaturalDocs::SymbolString->ToBinaryFile()> and
+         <NaturalDocs::SymbolString->FromBinaryFile()> are used to manipulate it, and the underlying format should be treated
+         as opaque.
+
+=cut
+
+
+use strict;
+use integer;
+
+use 5.005;  # When File::Spec was included by default
+
+use English '-no_match_vars';
+
+use FindBin;
+use lib "$FindBin::RealBin/Modules";
+
+sub INIT
+    {
+    # This function is just here so that when I start the debugger, it doesn't open a new file.  Normally it would jump to an INIT
+    # function in some other file since that's the first piece of code to execute.
+    };
+
+
+use NaturalDocs::Constants;
+use NaturalDocs::Version;
+use NaturalDocs::File;
+use NaturalDocs::Error;
+
+use NaturalDocs::ConfigFile;
+use NaturalDocs::BinaryFile;
+use NaturalDocs::StatusMessage;
+use NaturalDocs::SymbolString;
+use NaturalDocs::ReferenceString;
+use NaturalDocs::NDMarkup;
+
+use NaturalDocs::Settings;
+use NaturalDocs::Topics;
+use NaturalDocs::Languages;
+use NaturalDocs::Project;
+use NaturalDocs::Menu;
+use NaturalDocs::SymbolTable;
+use NaturalDocs::ClassHierarchy;
+use NaturalDocs::SourceDB;
+use NaturalDocs::ImageReferenceTable;
+use NaturalDocs::Parser;
+use NaturalDocs::Builder;
+
+
+
+###############################################################################
+#
+#   Group: Basic Types
+#
+#   Types used throughout the program.  As Perl is a weakly-typed language unless you box things into objects, these types are
+#   for documentation purposes and are not enforced.
+#
+#
+#   Type: FileName
+#
+#   A string representing the absolute, platform-dependent path to a file.  Relative file paths are no longer in use anywhere in the
+#   program.  All path manipulation should be done through <NaturalDocs::File>.
+#
+#
+#   Type: VersionInt
+#
+#   A comparable integer representing a version number.  Converting them to and from text and binary should be handled by
+#   <NaturalDocs::Version>.
+#
+#
+#   Type: SymbolString
+#
+#   A scalar which encodes a normalized array of identifier strings representing a full or partially-resolved symbol.  All symbols
+#   must be retrieved from plain text via <NaturalDocs::SymbolString->FromText()> so that the separation and normalization is
+#   always consistent.  SymbolStrings are comparable via string compare functions and are sortable.
+#
+#
+#   Type: ReferenceString
+#
+#   All the information about a reference that makes it unique encoded into a string.  This includes the <SymbolString> of the
+#   reference, the scope <SymbolString> it appears in, the scope <SymbolStrings> it has access to via "using", and the
+#   <ReferenceType>.  This is done because if any of those parameters change, it needs to be treated as a completely separate
+#   reference.
+#
+
+
+
+###############################################################################
+# Group: Support Functions
+# General functions that are used throughout the program, and that don't really fit anywhere else.
+
+
+#
+#   Function: StringCompare
+#
+#   Compares two strings so that the result is good for proper sorting.  A proper sort orders the characters as
+#   follows:
+#
+#   - End of string.
+#   - Whitespace.  Line break-tab-space.
+#   - Symbols, which is anything not included in the other entries.
+#   - Numbers, 0-9.
+#   - Letters, case insensitive except to break ties.
+#
+#   If you use cmp instead of this function, the result would go by ASCII/Unicode values which would place certain symbols
+#   between letters and numbers instead of having them all grouped together.  Also, you would have to choose between case
+#   sensitivity or complete case insensitivity, in which ties are broken arbitrarily.
+#
+#   Returns:
+#
+#   Like cmp, it returns zero if A and B are equal, a positive value if A is greater than B, and a negative value if A is less than B.
+#
+sub StringCompare #(a, b)
+    {
+    my ($a, $b) = @_;
+
+    if (!defined $a)
+        {
+        if (!defined $b)
+            {  return 0;  }
+        else
+            {  return -1;  };
+        }
+    elsif (!defined $b)
+        {
+        return 1;
+        };
+
+    my $translatedA = lc($a);
+    my $translatedB = lc($b);
+
+    $translatedA =~ tr/\n\r\t 0-9a-z/\x01\x02\x03\x04\xDB-\xFE/;
+    $translatedB =~ tr/\n\r\t 0-9a-z/\x01\x02\x03\x04\xDB-\xFE/;
+
+    my $result = $translatedA cmp $translatedB;
+
+    if ($result == 0)
+        {
+        # Break the tie by comparing their case.  Lowercase before uppercase.
+
+        # If statement just to keep everything theoretically kosher, even though in practice we don't need this.
+        if (ord('A') > ord('a'))
+            {  return ($a cmp $b);  }
+        else
+            {  return ($b cmp $a);  };
+        }
+    else
+        {  return $result;  };
+    };
+
+
+#
+#   Function: ShortenToMatchStrings
+#
+#   Compares two arrayrefs and shortens the first array to only contain shared entries.  Assumes all entries are strings.
+#
+#   Parameters:
+#
+#       sharedArrayRef - The arrayref that will be shortened to only contain common elements.
+#       compareArrayRef - The arrayref to match.
+#
+sub ShortenToMatchStrings #(sharedArrayRef, compareArrayRef)
+    {
+    my ($sharedArrayRef, $compareArrayRef) = @_;
+
+    my $index = 0;
+
+    while ($index < scalar @$sharedArrayRef && $index < scalar @$compareArrayRef &&
+             $sharedArrayRef->[$index] eq $compareArrayRef->[$index])
+        {  $index++;  };
+
+    if ($index < scalar @$sharedArrayRef)
+        {  splice(@$sharedArrayRef, $index);  };
+    };
+
+
+#
+#   Function: XChomp
+#
+#   A cross-platform chomp function.  Regular chomp fails when parsing Windows-format line breaks on a Unix platform.  It
+#   leaves the /r on, which screws everything up.  This does not.
+#
+#   Parameters:
+#
+#       lineRef - A *reference* to the line to chomp.
+#
+sub XChomp #(lineRef)
+    {
+    my $lineRef = shift;
+    $$lineRef =~ s/[\n\r]+$//;
+    };
+
+
+#
+#   Function: FindFirstSymbol
+#
+#   Searches a string for a number of symbols to see which appears first.
+#
+#   Parameters:
+#
+#       string - The string to search.
+#       symbols - An arrayref of symbols to look for.
+#       index - The index to start at, if any.
+#
+#   Returns:
+#
+#       The array ( index, symbol ).
+#
+#       index - The index the first symbol appears at, or -1 if none appear.
+#       symbol - The symbol that appeared, or undef if none.
+#
+sub FindFirstSymbol #(string, symbols, index)
+    {
+    my ($string, $symbols, $index) = @_;
+
+    if (!defined $index)
+        {  $index = 0;  };
+
+    my $lowestIndex = -1;
+    my $lowestSymbol;
+
+    foreach my $symbol (@$symbols)
+        {
+        my $testIndex = index($string, $symbol, $index);
+
+        if ($testIndex != -1 && ($lowestIndex == -1 || $testIndex < $lowestIndex))
+            {
+            $lowestIndex = $testIndex;
+            $lowestSymbol = $symbol;
+            };
+        };
+
+    return ($lowestIndex, $lowestSymbol);
+    };
+
+
+
+
+###############################################################################
+#
+#   Main Code
+#
+#   The order in which functions are called here is critically important.  Read the "Usage and Dependencies" sections of all the
+#   packages before even thinking about rearranging these.
+#
+
+
+eval {
+
+    # Check that our required packages are okay.
+
+    NaturalDocs::File->CheckCompatibility();
+
+
+    # Almost everything requires Settings to be initialized.
+
+    NaturalDocs::Settings->Load();
+
+
+    NaturalDocs::Project->LoadConfigFileInfo();
+
+    NaturalDocs::Topics->Load();
+    NaturalDocs::Languages->Load();
+
+
+    # Migrate from the old file names that were used prior to 1.14.
+
+    NaturalDocs::Project->MigrateOldFiles();
+
+
+    if (!NaturalDocs::Settings->IsQuiet())
+        {  print "Finding files and detecting changes...\n";  };
+
+    NaturalDocs::Project->LoadSourceFileInfo();
+    NaturalDocs::Project->LoadImageFileInfo();
+
+    # Register SourceDB extensions.  Order is important.
+    NaturalDocs::ImageReferenceTable->Register();
+
+    NaturalDocs::SymbolTable->Load();
+    NaturalDocs::ClassHierarchy->Load();
+    NaturalDocs::SourceDB->Load();
+
+    NaturalDocs::SymbolTable->Purge();
+    NaturalDocs::ClassHierarchy->Purge();
+    NaturalDocs::SourceDB->PurgeDeletedSourceFiles();
+
+
+    # Parse any supported files that have changed.
+
+    my $filesToParse = NaturalDocs::Project->FilesToParse();
+    my $amount = scalar keys %$filesToParse;
+
+    if ($amount > 0)
+        {
+        NaturalDocs::StatusMessage->Start('Parsing ' . $amount . ' file' . ($amount > 1 ? 's' : '') . '...', $amount);
+
+        foreach my $file (keys %$filesToParse)
+            {
+            NaturalDocs::Parser->ParseForInformation($file);
+            NaturalDocs::StatusMessage->CompletedItem();
+            };
+        };
+
+
+    # The symbol table is now fully resolved, so we can reduce its memory footprint.
+
+    NaturalDocs::SymbolTable->PurgeResolvingInfo();
+
+
+    # Load and update the menu file.  We need to do this after parsing so when it is updated, it will detect files where the
+    # default menu title has changed and files that have added or deleted Natural Docs content.
+
+    NaturalDocs::Menu->LoadAndUpdate();
+
+
+    # Build any files that need it.  This needs to be run regardless of whether there are any files to build.  It will handle its own
+    # output messages.
+
+    NaturalDocs::Builder->Run();
+
+
+    # Write the changes back to disk.
+
+    NaturalDocs::Menu->Save();
+    NaturalDocs::Project->SaveImageFileInfo();
+    NaturalDocs::Project->SaveSourceFileInfo();
+    NaturalDocs::SymbolTable->Save();
+    NaturalDocs::ClassHierarchy->Save();
+    NaturalDocs::SourceDB->Save();
+    NaturalDocs::Settings->Save();
+    NaturalDocs::Topics->Save();
+    NaturalDocs::Languages->Save();
+
+    # Must be done last.
+    NaturalDocs::Project->SaveConfigFileInfo();
+
+    if (!NaturalDocs::Settings->IsQuiet())
+        {  print "Done.\n";  };
+
+};
+
+if ($EVAL_ERROR)  # Oops.
+    {
+    NaturalDocs::Error->HandleDeath();
+    };
+

Added: trunk/build/NaturalDocs-1.4/NaturalDocs.bat
===================================================================
--- trunk/build/NaturalDocs-1.4/NaturalDocs.bat	                        (rev 0)
+++ trunk/build/NaturalDocs-1.4/NaturalDocs.bat	2009-07-01 15:30:00 UTC (rev 4275)
@@ -0,0 +1,17 @@
+ at echo off
+
+set NaturalDocsParams=
+
+rem Shift and loop so we can get more than nine parameters.
+rem This is especially important if we have spaces in file names.
+
+:MORE
+if "%1"=="" goto NOMORE
+set NaturalDocsParams=%NaturalDocsParams% %1
+shift
+goto MORE
+:NOMORE
+
+perl NaturalDocs %NaturalDocsParams%
+
+set NaturalDocsParams=
\ No newline at end of file

Added: trunk/build/NaturalDocs-1.4/Styles/Default.css
===================================================================
--- trunk/build/NaturalDocs-1.4/Styles/Default.css	                        (rev 0)
+++ trunk/build/NaturalDocs-1.4/Styles/Default.css	2009-07-01 15:30:00 UTC (rev 4275)
@@ -0,0 +1,767 @@
+/*
+   IMPORTANT: If you're editing this file in the output directory of one of
+   your projects, your changes will be overwritten the next time you run
+   Natural Docs.  Instead, copy this file to your project directory, make your
+   changes, and you can use it with -s.  Even better would be to make a CSS
+   file in your project directory with only your changes, which you can then
+   use with -s [original style] [your changes].
+
+   On the other hand, if you're editing this file in the Natural Docs styles
+   directory, the changes will automatically be applied to all your projects
+   that use this style the next time Natural Docs is run on them.
+
+   This file is part of Natural Docs, which is Copyright (C) 2003-2008 Greg Valure
+   Natural Docs is licensed under the GPL
+*/
+
+body {
+    font: 10pt Verdana, Arial, sans-serif;
+    color: #000000;
+    margin: 0; padding: 0;
+    }
+
+.ContentPage,
+.IndexPage,
+.FramedMenuPage {
+    background-color: #E8E8E8;
+    }
+.FramedContentPage,
+.FramedIndexPage,
+.FramedSearchResultsPage,
+.PopupSearchResultsPage {
+    background-color: #FFFFFF;
+    }
+
+
+a:link,
+a:visited { color: #900000; text-decoration: none }
+a:hover { color: #900000; text-decoration: underline }
+a:active { color: #FF0000; text-decoration: underline }
+
+td {
+    vertical-align: top }
+
+img { border: 0;  }
+
+
+/*
+    Comment out this line to use web-style paragraphs (blank line between
+    paragraphs, no indent) instead of print-style paragraphs (no blank line,
+    indented.)
+*/
+p {
+    text-indent: 5ex; margin: 0 }
+
+
+/*  Opera doesn't break with just wbr, but will if you add this.  */
+.Opera wbr:after {
+	content: "\00200B";
+	}
+
+
+/*  Blockquotes are used as containers for things that may need to scroll.  */
+blockquote {
+    padding: 0;
+    margin: 0;
+    overflow: auto;
+    }
+
+
+.Firefox1 blockquote {
+    padding-bottom: .5em;
+    }
+
+/*  Turn off scrolling when printing.  */
+ at media print {
+    blockquote {
+        overflow: visible;
+        }
+    .IE blockquote {
+        width: auto;
+        }
+    }
+
+
+
+#Menu {
+    font-size: 9pt;
+    padding: 10px 0 0 0;
+    }
+.ContentPage #Menu,
+.IndexPage #Menu {
+    position: absolute;
+    top: 0;
+    left: 0;
+    width: 31ex;
+    overflow: hidden;
+    }
+.ContentPage .Firefox #Menu,
+.IndexPage .Firefox #Menu {
+    width: 27ex;
+    }
+
+
+    .MTitle {
+        font-size: 16pt; font-weight: bold; font-variant: small-caps;
+        text-align: center;
+        padding: 5px 10px 15px 10px;
+        border-bottom: 1px dotted #000000;
+        margin-bottom: 15px }
+
+    .MSubTitle {
+        font-size: 9pt; font-weight: normal; font-variant: normal;
+        margin-top: 1ex; margin-bottom: 5px }
+
+
+    .MEntry a:link,
+    .MEntry a:hover,
+    .MEntry a:visited { color: #606060; margin-right: 0 }
+    .MEntry a:active { color: #A00000; margin-right: 0 }
+
+
+    .MGroup {
+        font-variant: small-caps; font-weight: bold;
+        margin: 1em 0 1em 10px;
+        }
+
+    .MGroupContent {
+        font-variant: normal; font-weight: normal }
+
+    .MGroup a:link,
+    .MGroup a:hover,
+    .MGroup a:visited { color: #545454; margin-right: 10px }
+    .MGroup a:active { color: #A00000; margin-right: 10px }
+
+
+    .MFile,
+    .MText,
+    .MLink,
+    .MIndex {
+        padding: 1px 17px 2px 10px;
+        margin: .25em 0 .25em 0;
+        }
+
+    .MText {
+        font-size: 8pt; font-style: italic }
+
+    .MLink {
+        font-style: italic }
+
+    #MSelected {
+        color: #000000; background-color: #FFFFFF;
+        /*  Replace padding with border.  */
+        padding: 0 10px 0 10px;
+        border-width: 1px 2px 2px 0; border-style: solid; border-color: #000000;
+        margin-right: 5px;
+        }
+
+    /*  Close off the left side when its in a group.  */
+    .MGroup #MSelected {
+        padding-left: 9px; border-left-width: 1px }
+
+    /*  A treat for Mozilla users.  Blatantly non-standard.  Will be replaced with CSS 3 attributes when finalized/supported.  */
+    .Firefox #MSelected {
+        -moz-border-radius-topright: 10px;
+        -moz-border-radius-bottomright: 10px }
+    .Firefox .MGroup #MSelected {
+        -moz-border-radius-topleft: 10px;
+        -moz-border-radius-bottomleft: 10px }
+
+
+    #MSearchPanel {
+        padding: 0px 6px;
+        margin: .25em 0;
+        }
+
+
+    #MSearchField {
+        font: italic 9pt Verdana, sans-serif;
+        color: #606060;
+        background-color: #E8E8E8;
+        border: none;
+        padding: 2px 4px;
+        width: 100%;
+        }
+    /* Only Opera gets it right. */
+    .Firefox #MSearchField,
+    .IE #MSearchField,
+    .Safari #MSearchField {
+        width: 94%;
+        }
+    .Opera9 #MSearchField,
+    .Konqueror #MSearchField {
+        width: 97%;
+        }
+    .FramedMenuPage .Firefox #MSearchField,
+    .FramedMenuPage .Safari #MSearchField,
+    .FramedMenuPage .Konqueror #MSearchField {
+        width: 98%;
+        }
+
+    /* Firefox doesn't do this right in frames without #MSearchPanel added on.
+        It's presence doesn't hurt anything other browsers. */
+    #MSearchPanel.MSearchPanelInactive:hover #MSearchField {
+        background-color: #FFFFFF;
+        border: 1px solid #C0C0C0;
+        padding: 1px 3px;
+        }
+    .MSearchPanelActive #MSearchField {
+        background-color: #FFFFFF;
+        border: 1px solid #C0C0C0;
+        font-style: normal;
+        padding: 1px 3px;
+        }
+
+    #MSearchType {
+        visibility: hidden;
+        font: 8pt Verdana, sans-serif;
+        width: 98%;
+        padding: 0;
+        border: 1px solid #C0C0C0;
+        }
+    .MSearchPanelActive #MSearchType,
+    /*  As mentioned above, Firefox doesn't do this right in frames without #MSearchPanel added on. */
+    #MSearchPanel.MSearchPanelInactive:hover #MSearchType,
+    #MSearchType:focus {
+        visibility: visible;
+        color: #606060;
+        }
+    #MSearchType option#MSearchEverything {
+        font-weight: bold;
+        }
+
+    .Opera8 .MSearchPanelInactive:hover,
+    .Opera8 .MSearchPanelActive {
+        margin-left: -1px;
+        }
+
+
+    iframe#MSearchResults {
+        width: 60ex;
+        height: 15em;
+        }
+    #MSearchResultsWindow {
+        display: none;
+        position: absolute;
+        left: 0; top: 0;
+        border: 1px solid #000000;
+        background-color: #E8E8E8;
+        }
+    #MSearchResultsWindowClose {
+        font-weight: bold;
+        font-size: 8pt;
+        display: block;
+        padding: 2px 5px;
+        }
+    #MSearchResultsWindowClose:link,
+    #MSearchResultsWindowClose:visited {
+        color: #000000;
+        text-decoration: none;
+        }
+    #MSearchResultsWindowClose:active,
+    #MSearchResultsWindowClose:hover {
+        color: #800000;
+        text-decoration: none;
+        background-color: #F4F4F4;
+        }
+
+
+
+
+#Content {
+    padding-bottom: 15px;
+    }
+
+.ContentPage #Content {
+    border-width: 0 0 1px 1px;
+    border-style: solid;
+    border-color: #000000;
+    background-color: #FFFFFF;
+    font-size: 9pt;  /* To make 31ex match the menu's 31ex. */
+    margin-left: 31ex;
+    }
+.ContentPage .Firefox #Content {
+    margin-left: 27ex;
+    }
+
+
+
+    .CTopic {
+        font-size: 10pt;
+        margin-bottom: 3em;
+        }
+
+
+    .CTitle {
+        font-size: 12pt; font-weight: bold;
+        border-width: 0 0 1px 0; border-style: solid; border-color: #A0A0A0;
+        margin: 0 15px .5em 15px }
+
+    .CGroup .CTitle {
+        font-size: 16pt; font-variant: small-caps;
+        padding-left: 15px; padding-right: 15px;
+        border-width: 0 0 2px 0; border-color: #000000;
+        margin-left: 0; margin-right: 0 }
+
+    .CClass .CTitle,
+    .CInterface .CTitle,
+    .CDatabase .CTitle,
+    .CDatabaseTable .CTitle,
+    .CSection .CTitle {
+        font-size: 18pt;
+        color: #FFFFFF; background-color: #A0A0A0;
+        padding: 10px 15px 10px 15px;
+        border-width: 2px 0; border-color: #000000;
+        margin-left: 0; margin-right: 0 }
+
+    #MainTopic .CTitle {
+        font-size: 20pt;
+        color: #FFFFFF; background-color: #7070C0;
+        padding: 10px 15px 10px 15px;
+        border-width: 0 0 3px 0; border-color: #000000;
+        margin-left: 0; margin-right: 0 }
+
+    .CBody {
+        margin-left: 15px; margin-right: 15px }
+
+
+    .CToolTip {
+        position: absolute; visibility: hidden;
+        left: 0; top: 0;
+        background-color: #FFFFE0;
+        padding: 5px;
+        border-width: 1px 2px 2px 1px; border-style: solid; border-color: #000000;
+        font-size: 8pt;
+        }
+
+    .Opera .CToolTip {
+        max-width: 98%;
+        }
+
+    /*  Scrollbars would be useless.  */
+    .CToolTip blockquote {
+        overflow: hidden;
+        }
+    .IE6 .CToolTip blockquote {
+        overflow: visible;
+        }
+
+    .CHeading {
+        font-weight: bold; font-size: 10pt;
+        margin: 1.5em 0 .5em 0;
+        }
+
+    .CBody pre {
+        font: 10pt "Courier New", Courier, monospace;
+        margin: 1em 0;
+        }
+
+    .CBody ul {
+        /*  I don't know why CBody's margin doesn't apply, but it's consistent across browsers so whatever.
+             Reapply it here as padding.  */
+        padding-left: 15px; padding-right: 15px;
+        margin: .5em 5ex .5em 5ex;
+        }
+
+    .CDescriptionList {
+        margin: .5em 5ex 0 5ex }
+
+        .CDLEntry {
+            font: 10pt "Courier New", Courier, monospace; color: #808080;
+            padding-bottom: .25em;
+            white-space: nowrap }
+
+        .CDLDescription {
+            font-size: 10pt;  /*  For browsers that don't inherit correctly, like Opera 5.  */
+            padding-bottom: .5em; padding-left: 5ex }
+
+
+    .CTopic img {
+        text-align: center;
+        display: block;
+        margin: 1em auto;
+        }
+    .CImageCaption {
+        font-variant: small-caps;
+        font-size: 8pt;
+        color: #808080;
+        text-align: center;
+        position: relative;
+        top: 1em;
+        }
+
+    .CImageLink {
+        color: #808080;
+        font-style: italic;
+        }
+    a.CImageLink:link,
+    a.CImageLink:visited,
+    a.CImageLink:hover { color: #808080 }
+
+
+
+
+
+.Prototype {
+    font: 10pt "Courier New", Courier, monospace;
+    padding: 5px 3ex;
+    border-width: 1px; border-style: solid;
+    margin: 0 5ex 1.5em 5ex;
+    }
+
+    .Prototype td {
+        font-size: 10pt;
+        }
+
+    .PDefaultValue,
+    .PDefaultValuePrefix,
+    .PTypePrefix {
+        color: #8F8F8F;
+        }
+    .PTypePrefix {
+        text-align: right;
+        }
+    .PAfterParameters {
+        vertical-align: bottom;
+        }
+
+    .IE .Prototype table {
+        padding: 0;
+        }
+
+    .CFunction .Prototype {
+        background-color: #F4F4F4; border-color: #D0D0D0 }
+    .CProperty .Prototype {
+        background-color: #F4F4FF; border-color: #C0C0E8 }
+    .CVariable .Prototype {
+        background-color: #FFFFF0; border-color: #E0E0A0 }
+
+    .CClass .Prototype {
+        border-width: 1px 2px 2px 1px; border-style: solid; border-color: #A0A0A0;
+        background-color: #F4F4F4;
+        }
+    .CInterface .Prototype {
+        border-width: 1px 2px 2px 1px; border-style: solid; border-color: #A0A0D0;
+        background-color: #F4F4FF;
+        }
+
+    .CDatabaseIndex .Prototype,
+    .CConstant .Prototype {
+        background-color: #D0D0D0; border-color: #000000 }
+    .CType .Prototype,
+    .CEnumeration .Prototype {
+        background-color: #FAF0F0; border-color: #E0B0B0;
+        }
+    .CDatabaseTrigger .Prototype,
+    .CEvent .Prototype,
+    .CDelegate .Prototype {
+        background-color: #F0FCF0; border-color: #B8E4B8 }
+
+    .CToolTip .Prototype {
+        margin: 0 0 .5em 0;
+        white-space: nowrap;
+        }
+
+
+
+
+
+.Summary {
+    margin: 1.5em 5ex 0 5ex }
+
+    .STitle {
+        font-size: 12pt; font-weight: bold;
+        margin-bottom: .5em }
+
+
+    .SBorder {
+        background-color: #FFFFF0;
+        padding: 15px;
+        border: 1px solid #C0C060 }
+
+    /* In a frame IE 6 will make them too long unless you set the width to 100%.  Without frames it will be correct without a width
+        or slightly too long (but not enough to scroll) with a width.  This arbitrary weirdness simply astounds me.  IE 7 has the same
+        problem with frames, haven't tested it without.  */
+    .FramedContentPage .IE .SBorder {
+        width: 100% }
+
+    /*  A treat for Mozilla users.  Blatantly non-standard.  Will be replaced with CSS 3 attributes when finalized/supported.  */
+    .Firefox .SBorder {
+        -moz-border-radius: 20px }
+
+
+    .STable {
+        font-size: 9pt; width: 100% }
+
+    .SEntry {
+        width: 30% }
+    .SDescription {
+        width: 70% }
+
+
+    .SMarked {
+        background-color: #F8F8D8 }
+
+    .SDescription { padding-left: 2ex }
+    .SIndent1 .SEntry { padding-left: 1.5ex }   .SIndent1 .SDescription { padding-left: 3.5ex }
+    .SIndent2 .SEntry { padding-left: 3.0ex }   .SIndent2 .SDescription { padding-left: 5.0ex }
+    .SIndent3 .SEntry { padding-left: 4.5ex }   .SIndent3 .SDescription { padding-left: 6.5ex }
+    .SIndent4 .SEntry { padding-left: 6.0ex }   .SIndent4 .SDescription { padding-left: 8.0ex }
+    .SIndent5 .SEntry { padding-left: 7.5ex }   .SIndent5 .SDescription { padding-left: 9.5ex }
+
+    .SDescription a { color: #800000}
+    .SDescription a:active { color: #A00000 }
+
+    .SGroup td {
+        padding-top: .5em; padding-bottom: .25em }
+
+    .SGroup .SEntry {
+        font-weight: bold; font-variant: small-caps }
+
+    .SGroup .SEntry a { color: #800000 }
+    .SGroup .SEntry a:active { color: #F00000 }
+
+
+    .SMain td,
+    .SClass td,
+    .SDatabase td,
+    .SDatabaseTable td,
+    .SSection td {
+        font-size: 10pt;
+        padding-bottom: .25em }
+
+    .SClass td,
+    .SDatabase td,
+    .SDatabaseTable td,
+    .SSection td {
+        padding-top: 1em }
+
+    .SMain .SEntry,
+    .SClass .SEntry,
+    .SDatabase .SEntry,
+    .SDatabaseTable .SEntry,
+    .SSection .SEntry {
+        font-weight: bold;
+        }
+
+    .SMain .SEntry a,
+    .SClass .SEntry a,
+    .SDatabase .SEntry a,
+    .SDatabaseTable .SEntry a,
+    .SSection .SEntry a { color: #000000 }
+
+    .SMain .SEntry a:active,
+    .SClass .SEntry a:active,
+    .SDatabase .SEntry a:active,
+    .SDatabaseTable .SEntry a:active,
+    .SSection .SEntry a:active { color: #A00000 }
+
+
+
+
+
+.ClassHierarchy {
+    margin: 0 15px 1em 15px }
+
+    .CHEntry {
+        border-width: 1px 2px 2px 1px; border-style: solid; border-color: #A0A0A0;
+        margin-bottom: 3px;
+        padding: 2px 2ex;
+        font-size: 10pt;
+        background-color: #F4F4F4; color: #606060;
+        }
+
+    .Firefox .CHEntry {
+        -moz-border-radius: 4px;
+        }
+
+    .CHCurrent .CHEntry {
+        font-weight: bold;
+        border-color: #000000;
+        color: #000000;
+        }
+
+    .CHChildNote .CHEntry {
+        font-style: italic;
+        font-size: 8pt;
+        }
+
+    .CHIndent {
+        margin-left: 3ex;
+        }
+
+    .CHEntry a:link,
+    .CHEntry a:visited,
+    .CHEntry a:hover {
+        color: #606060;
+        }
+    .CHEntry a:active {
+        color: #800000;
+        }
+
+
+
+
+
+#Index {
+    background-color: #FFFFFF;
+    }
+
+/*  As opposed to .PopupSearchResultsPage #Index  */
+.IndexPage #Index,
+.FramedIndexPage #Index,
+.FramedSearchResultsPage #Index {
+    padding: 15px;
+    }
+
+.IndexPage #Index {
+    border-width: 0 0 1px 1px;
+    border-style: solid;
+    border-color: #000000;
+    font-size: 9pt;  /* To make 27ex match the menu's 27ex. */
+    margin-left: 27ex;
+    }
+
+
+    .IPageTitle {
+        font-size: 20pt; font-weight: bold;
+        color: #FFFFFF; background-color: #7070C0;
+        padding: 10px 15px 10px 15px;
+        border-width: 0 0 3px 0; border-color: #000000; border-style: solid;
+        margin: -15px -15px 0 -15px }
+
+    .FramedSearchResultsPage .IPageTitle {
+        margin-bottom: 15px;
+        }
+
+    .INavigationBar {
+        font-size: 10pt;
+        text-align: center;
+        background-color: #FFFFF0;
+        padding: 5px;
+        border-bottom: solid 1px black;
+        margin: 0 -15px 15px -15px;
+        }
+
+    .INavigationBar a {
+        font-weight: bold }
+
+    .IHeading {
+        font-size: 16pt; font-weight: bold;
+        padding: 2.5em 0 .5em 0;
+        text-align: center;
+        width: 3.5ex;
+        }
+    #IFirstHeading {
+        padding-top: 0;
+        }
+
+    .IEntry {
+        font-size: 10pt;
+        padding-left: 1ex;
+        }
+    .PopupSearchResultsPage .IEntry {
+        font-size: 8pt;
+        padding: 1px 5px;
+        }
+    .PopupSearchResultsPage .Opera9 .IEntry,
+    .FramedSearchResultsPage .Opera9 .IEntry {
+        text-align: left;
+        }
+    .FramedSearchResultsPage .IEntry {
+        padding: 0;
+        }
+
+    .ISubIndex {
+        padding-left: 3ex; padding-bottom: .5em }
+    .PopupSearchResultsPage .ISubIndex {
+        display: none;
+        }
+
+    /*  While it may cause some entries to look like links when they aren't, I found it's much easier to read the
+         index if everything's the same color.  */
+    .ISymbol {
+        font-weight: bold; color: #900000  }
+
+    .IndexPage .ISymbolPrefix,
+    .FramedIndexPage .ISymbolPrefix {
+        font-size: 10pt;
+        text-align: right;
+        color: #C47C7C;
+        background-color: #F8F8F8;
+        border-right: 3px solid #E0E0E0;
+        border-left: 1px solid #E0E0E0;
+        padding: 0 1px 0 2px;
+        }
+    .PopupSearchResultsPage .ISymbolPrefix,
+    .FramedSearchResultsPage .ISymbolPrefix {
+        color: #900000;
+        }
+    .PopupSearchResultsPage .ISymbolPrefix {
+        font-size: 8pt;
+        }
+
+    .IndexPage #IFirstSymbolPrefix,
+    .FramedIndexPage #IFirstSymbolPrefix {
+        border-top: 1px solid #E0E0E0;
+        }
+    .IndexPage #ILastSymbolPrefix,
+    .FramedIndexPage #ILastSymbolPrefix {
+        border-bottom: 1px solid #E0E0E0;
+        }
+    .IndexPage #IOnlySymbolPrefix,
+    .FramedIndexPage #IOnlySymbolPrefix {
+        border-top: 1px solid #E0E0E0;
+        border-bottom: 1px solid #E0E0E0;
+        }
+
+    a.IParent,
+    a.IFile {
+        display: block;
+        }
+
+    .PopupSearchResultsPage .SRStatus {
+        padding: 2px 5px;
+        font-size: 8pt;
+        font-style: italic;
+        }
+    .FramedSearchResultsPage .SRStatus {
+        font-size: 10pt;
+        font-style: italic;
+        }
+
+    .SRResult {
+        display: none;
+        }
+
+
+
+#Footer {
+    font-size: 8pt;
+    color: #989898;
+    text-align: right;
+    }
+
+#Footer p {
+    text-indent: 0;
+    margin-bottom: .5em;
+    }
+
+.ContentPage #Footer,
+.IndexPage #Footer {
+    text-align: right;
+    margin: 2px;
+    }
+
+.FramedMenuPage #Footer {
+    text-align: center;
+    margin: 5em 10px 10px 10px;
+    padding-top: 1em;
+    border-top: 1px solid #C8C8C8;
+    }
+
+    #Footer a:link,
+    #Footer a:hover,
+    #Footer a:visited { color: #989898 }
+    #Footer a:active { color: #A00000 }
+

Added: trunk/build/NaturalDocs-1.4/Styles/Roman.css
===================================================================
--- trunk/build/NaturalDocs-1.4/Styles/Roman.css	                        (rev 0)
+++ trunk/build/NaturalDocs-1.4/Styles/Roman.css	2009-07-01 15:30:00 UTC (rev 4275)
@@ -0,0 +1,765 @@
+/*
+   IMPORTANT: If you're editing this file in the output directory of one of
+   your projects, your changes will be overwritten the next time you run
+   Natural Docs.  Instead, copy this file to your project directory, make your
+   changes, and you can use it with -s.  Even better would be to make a CSS
+   file in your project directory with only your changes, which you can then
+   use with -s [original style] [your changes].
+
+   On the other hand, if you're editing this file in the Natural Docs styles
+   directory, the changes will automatically be applied to all your projects
+   that use this style the next time Natural Docs is run on them.
+
+   This file is part of Natural Docs, which is Copyright (C) 2003-2008 Greg Valure
+   Natural Docs is licensed under the GPL
+*/
+
+body {
+    font: 12pt "Times New Roman", Roman, serif;
+    color: #000000;
+    margin: 0; padding: 0;
+    }
+
+.ContentPage,
+.IndexPage,
+.FramedMenuPage {
+    background-color: #E8E8E8;
+    }
+.FramedContentPage,
+.FramedIndexPage,
+.FramedSearchResultsPage,
+.PopupSearchResultsPage {
+    background-color: #FFFFFF;
+    }
+
+
+a:link,
+a:visited { color: #900000; text-decoration: none }
+a:hover { color: #900000; text-decoration: underline }
+a:active { color: #FF0000; text-decoration: underline }
+
+td {
+    vertical-align: top }
+
+img { border: 0;  }
+
+
+/*
+    Comment out this line to use web-style paragraphs (blank line between
+    paragraphs, no indent) instead of print-style paragraphs (no blank line,
+    indented.)
+*/
+p {
+    text-indent: 5ex; margin: 0 }
+
+
+/*  Opera doesn't break with just wbr, but will if you add this.  */
+.Opera wbr:after {
+	content: "\00200B";
+	}
+
+/*  Blockquotes are used as containers for things that may need to scroll.  */
+blockquote {
+    padding: 0;
+    margin: 0;
+    overflow: auto;
+    }
+
+
+.Firefox1 blockquote {
+    padding-bottom: .5em;
+    }
+
+/*  Turn off scrolling when printing.  */
+ at media print {
+    blockquote {
+        overflow: visible;
+        }
+    .IE blockquote {
+        width: auto;
+        }
+    }
+
+
+
+#Menu {
+    font-size: 10pt;
+    padding: 10px 0 0 0;
+    }
+.ContentPage #Menu,
+.IndexPage #Menu {
+    position: absolute;
+    top: 0;
+    left: 0;
+    width: 31ex;
+    overflow: hidden;
+    }
+.ContentPage .Firefox #Menu,
+.IndexPage .Firefox #Menu {
+    width: 27ex;
+    }
+
+
+    .MTitle {
+        font-size: 18pt; font-weight: bold; font-variant: small-caps;
+        text-align: center;
+        padding: 5px 10px 15px 10px;
+        border-bottom: 1px dotted #000000;
+        margin-bottom: 15px }
+
+    .MSubTitle {
+        font-size: 10pt; font-weight: normal; font-variant: normal;
+        margin-top: 1ex; margin-bottom: 5px }
+
+
+    .MEntry a:link,
+    .MEntry a:hover,
+    .MEntry a:visited { color: #606060; margin-right: 0 }
+    .MEntry a:active { color: #A00000; margin-right: 0 }
+
+
+    .MGroup {
+        font-variant: small-caps; font-weight: bold;
+        margin: 1em 0 1em 10px;
+        }
+
+    .MGroupContent {
+        font-variant: normal; font-weight: normal }
+
+    .MGroup a:link,
+    .MGroup a:hover,
+    .MGroup a:visited { color: #545454; margin-right: 10px }
+    .MGroup a:active { color: #A00000; margin-right: 10px }
+
+
+    .MFile,
+    .MText,
+    .MLink,
+    .MIndex {
+        padding: 1px 17px 2px 10px;
+        margin: .25em 0 .25em 0;
+        }
+
+    .MText {
+        font-size: 8pt; font-style: italic }
+
+    .MLink {
+        font-style: italic }
+
+    #MSelected {
+        color: #000000; background-color: #FFFFFF;
+        /*  Replace padding with border.  */
+        padding: 0 10px 0 10px;
+        border-width: 1px 2px 2px 0; border-style: solid; border-color: #000000;
+        margin-right: 5px;
+        }
+
+    /*  Close off the left side when its in a group.  */
+    .MGroup #MSelected {
+        padding-left: 9px; border-left-width: 1px }
+
+    /*  A treat for Mozilla users.  Blatantly non-standard.  Will be replaced with CSS 3 attributes when finalized/supported.  */
+    .Firefox #MSelected {
+        -moz-border-radius-topright: 10px;
+        -moz-border-radius-bottomright: 10px }
+    .Firefox .MGroup #MSelected {
+        -moz-border-radius-topleft: 10px;
+        -moz-border-radius-bottomleft: 10px }
+
+
+    #MSearchPanel {
+        padding: 0px 6px;
+        margin: .25em 0;
+        }
+
+
+    #MSearchField {
+        font: italic 10pt "Times New Roman", Roman, serif;
+        color: #606060;
+        background-color: #E8E8E8;
+        border: none;
+        padding: 2px 4px;
+        width: 100%;
+        }
+    /* Only Opera gets it right. */
+    .Firefox #MSearchField,
+    .IE #MSearchField,
+    .Safari #MSearchField {
+        width: 94%;
+        }
+    .Opera9 #MSearchField,
+    .Konqueror #MSearchField {
+        width: 97%;
+        }
+    .FramedMenuPage .Firefox #MSearchField,
+    .FramedMenuPage .Safari #MSearchField,
+    .FramedMenuPage .Konqueror #MSearchField {
+        width: 98%;
+        }
+
+    /* Firefox doesn't do this right in frames without #MSearchPanel added on.
+        It's presence doesn't hurt anything other browsers. */
+    #MSearchPanel.MSearchPanelInactive:hover #MSearchField {
+        background-color: #FFFFFF;
+        border: 1px solid #C0C0C0;
+        padding: 1px 3px;
+        }
+    .MSearchPanelActive #MSearchField {
+        background-color: #FFFFFF;
+        border: 1px solid #C0C0C0;
+        font-style: normal;
+        padding: 1px 3px;
+        }
+
+    #MSearchType {
+        visibility: hidden;
+        font: 10pt "Times New Roman", Roman, serif;
+        width: 98%;
+        padding: 0;
+        border: 1px solid #C0C0C0;
+        }
+    .MSearchPanelActive #MSearchType,
+    /*  As mentioned above, Firefox doesn't do this right in frames without #MSearchPanel added on. */
+    #MSearchPanel.MSearchPanelInactive:hover #MSearchType,
+    #MSearchType:focus {
+        visibility: visible;
+        color: #606060;
+        }
+    #MSearchType option#MSearchEverything {
+        font-weight: bold;
+        }
+
+    .Opera8 .MSearchPanelInactive:hover,
+    .Opera8 .MSearchPanelActive {
+        margin-left: -1px;
+        }
+
+
+    iframe#MSearchResults {
+        width: 60ex;
+        height: 15em;
+        }
+    #MSearchResultsWindow {
+        display: none;
+        position: absolute;
+        left: 0; top: 0;
+        border: 1px solid #000000;
+        background-color: #E8E8E8;
+        }
+    #MSearchResultsWindowClose {
+        font-weight: bold;
+        font-size: 8pt;
+        display: block;
+        padding: 2px 5px;
+        }
+    #MSearchResultsWindowClose:link,
+    #MSearchResultsWindowClose:visited {
+        color: #000000;
+        text-decoration: none;
+        }
+    #MSearchResultsWindowClose:active,
+    #MSearchResultsWindowClose:hover {
+        color: #800000;
+        text-decoration: none;
+        background-color: #F4F4F4;
+        }
+
+
+
+
+#Content {
+    padding-bottom: 15px;
+    }
+
+.ContentPage #Content {
+    border-width: 0 0 1px 1px;
+    border-style: solid;
+    border-color: #000000;
+    background-color: #FFFFFF;
+    font-size: 10pt;  /* To make 31ex match the menu's 31ex. */
+    margin-left: 31ex;
+    }
+.ContentPage .Firefox #Content {
+    margin-left: 27ex;
+    }
+
+
+
+    .CTopic {
+        font-size: 12pt;
+        margin-bottom: 3em;
+        }
+
+
+    .CTitle {
+        font-size: 16pt; font-weight: bold;
+        border-width: 0 0 1px 0; border-style: solid; border-color: #A0A0A0;
+        margin: 0 15px .5em 15px }
+
+    .CGroup .CTitle {
+        font-size: 18pt; font-variant: small-caps;
+        padding-left: 15px; padding-right: 15px;
+        border-width: 0 0 2px 0; border-color: #000000;
+        margin-left: 0; margin-right: 0 }
+
+    .CClass .CTitle,
+    .CInterface .CTitle,
+    .CDatabase .CTitle,
+    .CDatabaseTable .CTitle,
+    .CSection .CTitle {
+        font-size: 20pt;
+        color: #FFFFFF; background-color: #A0A0A0;
+        padding: 10px 15px 10px 15px;
+        border-width: 2px 0; border-color: #000000;
+        margin-left: 0; margin-right: 0 }
+
+    #MainTopic .CTitle {
+        font-size: 24pt;
+        color: #FFFFFF; background-color: #7070C0;
+        padding: 10px 15px 10px 15px;
+        border-width: 0 0 3px 0; border-color: #000000;
+        margin-left: 0; margin-right: 0 }
+
+    .CBody {
+        margin-left: 15px; margin-right: 15px }
+
+
+    .CToolTip {
+        position: absolute; visibility: hidden;
+        left: 0; top: 0;
+        background-color: #FFFFE0;
+        padding: 5px;
+        border-width: 1px 2px 2px 1px; border-style: solid; border-color: #000000;
+        font-size: 10pt;
+        }
+
+    .Opera .CToolTip {
+        max-width: 98%;
+        }
+
+    /*  Scrollbars would be useless.  */
+    .CToolTip blockquote {
+        overflow: hidden;
+        }
+    .IE6 .CToolTip blockquote {
+        overflow: visible;
+        }
+
+    .CHeading {
+        font-weight: bold; font-size: 10pt;
+        margin: 1.5em 0 .5em 0;
+        }
+
+    .CBody pre {
+        font: 10pt "Courier New", Courier, monospace;
+        margin: 1em 0;
+        }
+
+    .CBody ul {
+        /*  I don't know why CBody's margin doesn't apply, but it's consistent across browsers so whatever.
+             Reapply it here as padding.  */
+        padding-left: 15px; padding-right: 15px;
+        margin: .5em 5ex .5em 5ex;
+        }
+
+    .CDescriptionList {
+        margin: .5em 5ex 0 5ex }
+
+        .CDLEntry {
+            font: 10pt "Courier New", Courier, monospace; color: #808080;
+            padding-bottom: .25em;
+            white-space: nowrap }
+
+        .CDLDescription {
+            font-size: 12pt;  /*  For browsers that don't inherit correctly, like Opera 5.  */
+            padding-bottom: .5em; padding-left: 5ex }
+
+
+    .CTopic img {
+        text-align: center;
+        display: block;
+        margin: 1em auto;
+        }
+    .CImageCaption {
+        font-variant: small-caps;
+        font-size: 10pt;
+        color: #808080;
+        text-align: center;
+        position: relative;
+        top: 1em;
+        }
+
+    .CImageLink {
+        color: #808080;
+        font-style: italic;
+        }
+    a.CImageLink:link,
+    a.CImageLink:visited,
+    a.CImageLink:hover { color: #808080 }
+
+
+
+
+
+.Prototype {
+    font: 10pt "Courier New", Courier, monospace;
+    padding: 5px 3ex;
+    border-width: 1px; border-style: solid;
+    margin: 0 5ex 1.5em 5ex;
+    }
+
+    .Prototype td {
+        font-size: 10pt;
+        }
+
+    .PDefaultValue,
+    .PDefaultValuePrefix,
+    .PTypePrefix {
+        color: #8F8F8F;
+        }
+    .PTypePrefix {
+        text-align: right;
+        }
+    .PAfterParameters {
+        vertical-align: bottom;
+        }
+
+    .IE .Prototype table {
+        padding: 0;
+        }
+
+    .CFunction .Prototype {
+        background-color: #F4F4F4; border-color: #D0D0D0 }
+    .CProperty .Prototype {
+        background-color: #F4F4FF; border-color: #C0C0E8 }
+    .CVariable .Prototype {
+        background-color: #FFFFF0; border-color: #E0E0A0 }
+
+    .CClass .Prototype {
+        border-width: 1px 2px 2px 1px; border-style: solid; border-color: #A0A0A0;
+        background-color: #F4F4F4;
+        }
+    .CInterface .Prototype {
+        border-width: 1px 2px 2px 1px; border-style: solid; border-color: #A0A0D0;
+        background-color: #F4F4FF;
+        }
+
+    .CDatabaseIndex .Prototype,
+    .CConstant .Prototype {
+        background-color: #D0D0D0; border-color: #000000 }
+    .CType .Prototype,
+    .CEnumeration .Prototype {
+        background-color: #FAF0F0; border-color: #E0B0B0;
+        }
+    .CDatabaseTrigger .Prototype,
+    .CEvent .Prototype,
+    .CDelegate .Prototype {
+        background-color: #F0FCF0; border-color: #B8E4B8 }
+
+    .CToolTip .Prototype {
+        margin: 0 0 .5em 0;
+        white-space: nowrap;
+        }
+
+
+
+
+
+.Summary {
+    margin: 1.5em 5ex 0 5ex }
+
+    .STitle {
+        font-size: 14pt; font-weight: bold;
+        margin-bottom: .5em }
+
+
+    .SBorder {
+        background-color: #FFFFF0;
+        padding: 15px;
+        border: 1px solid #C0C060 }
+
+    /* In a frame IE 6 will make them too long unless you set the width to 100%.  Without frames it will be correct without a width
+        or slightly too long (but not enough to scroll) with a width.  This arbitrary weirdness simply astounds me.  IE 7 has the same
+        problem with frames, haven't tested it without.  */
+    .FramedContentPage .IE .SBorder {
+        width: 100% }
+
+    /*  A treat for Mozilla users.  Blatantly non-standard.  Will be replaced with CSS 3 attributes when finalized/supported.  */
+    .Firefox .SBorder {
+        -moz-border-radius: 20px }
+
+
+    .STable {
+        font-size: 10pt; width: 100% }
+
+    .SEntry {
+        width: 30% }
+    .SDescription {
+        width: 70% }
+
+
+    .SMarked {
+        background-color: #F8F8D8 }
+
+    .SDescription { padding-left: 2ex }
+    .SIndent1 .SEntry { padding-left: 1.5ex }   .SIndent1 .SDescription { padding-left: 3.5ex }
+    .SIndent2 .SEntry { padding-left: 3.0ex }   .SIndent2 .SDescription { padding-left: 5.0ex }
+    .SIndent3 .SEntry { padding-left: 4.5ex }   .SIndent3 .SDescription { padding-left: 6.5ex }
+    .SIndent4 .SEntry { padding-left: 6.0ex }   .SIndent4 .SDescription { padding-left: 8.0ex }
+    .SIndent5 .SEntry { padding-left: 7.5ex }   .SIndent5 .SDescription { padding-left: 9.5ex }
+
+    .SDescription a { color: #800000}
+    .SDescription a:active { color: #A00000 }
+
+    .SGroup td {
+        padding-top: .5em; padding-bottom: .25em }
+
+    .SGroup .SEntry {
+        font-weight: bold; font-variant: small-caps }
+
+    .SGroup .SEntry a { color: #800000 }
+    .SGroup .SEntry a:active { color: #F00000 }
+
+
+    .SMain td,
+    .SClass td,
+    .SDatabase td,
+    .SDatabaseTable td,
+    .SSection td {
+        font-size: 12pt;
+        padding-bottom: .25em }
+
+    .SClass td,
+    .SDatabase td,
+    .SDatabaseTable td,
+    .SSection td {
+        padding-top: 1em }
+
+    .SMain .SEntry,
+    .SClass .SEntry,
+    .SDatabase .SEntry,
+    .SDatabaseTable .SEntry,
+    .SSection .SEntry {
+        font-weight: bold;
+        }
+
+    .SMain .SEntry a,
+    .SClass .SEntry a,
+    .SDatabase .SEntry a,
+    .SDatabaseTable .SEntry a,
+    .SSection .SEntry a { color: #000000 }
+
+    .SMain .SEntry a:active,
+    .SClass .SEntry a:active,
+    .SDatabase .SEntry a:active,
+    .SDatabaseTable .SEntry a:active,
+    .SSection .SEntry a:active { color: #A00000 }
+
+
+
+
+
+.ClassHierarchy {
+    margin: 0 15px 1em 15px }
+
+    .CHEntry {
+        border-width: 1px 2px 2px 1px; border-style: solid; border-color: #A0A0A0;
+        margin-bottom: 3px;
+        padding: 2px 2ex;
+        font-size: 12pt;
+        background-color: #F4F4F4; color: #606060;
+        }
+
+    .Firefox .CHEntry {
+        -moz-border-radius: 4px;
+        }
+
+    .CHCurrent .CHEntry {
+        font-weight: bold;
+        border-color: #000000;
+        color: #000000;
+        }
+
+    .CHChildNote .CHEntry {
+        font-style: italic;
+        font-size: 10pt;
+        }
+
+    .CHIndent {
+        margin-left: 3ex;
+        }
+
+    .CHEntry a:link,
+    .CHEntry a:visited,
+    .CHEntry a:hover {
+        color: #606060;
+        }
+    .CHEntry a:active {
+        color: #800000;
+        }
+
+
+
+
+
+#Index {
+    background-color: #FFFFFF;
+    }
+
+/*  As opposed to .PopupSearchResultsPage #Index  */
+.IndexPage #Index,
+.FramedIndexPage #Index,
+.FramedSearchResultsPage #Index {
+    padding: 15px;
+    }
+
+.IndexPage #Index {
+    border-width: 0 0 1px 1px;
+    border-style: solid;
+    border-color: #000000;
+    font-size: 10pt;  /* To make 27ex match the menu's 27ex. */
+    margin-left: 27ex;
+    }
+
+
+    .IPageTitle {
+        font-size: 24pt; font-weight: bold;
+        color: #FFFFFF; background-color: #7070C0;
+        padding: 10px 15px 10px 15px;
+        border-width: 0 0 3px 0; border-color: #000000; border-style: solid;
+        margin: -15px -15px 0 -15px }
+
+    .FramedSearchResultsPage .IPageTitle {
+        margin-bottom: 15px;
+        }
+
+    .INavigationBar {
+        text-align: center;
+        background-color: #FFFFF0;
+        padding: 5px;
+        border-bottom: solid 1px black;
+        margin: 0 -15px 15px -15px;
+        }
+
+    .INavigationBar a {
+        font-weight: bold }
+
+    .IHeading {
+        font-size: 20pt; font-weight: bold;
+        padding: 2.5em 0 .5em 0;
+        text-align: center;
+        width: 3.5ex;
+        }
+    #IFirstHeading {
+        padding-top: 0;
+        }
+
+    .IEntry {
+        font-size: 12pt;
+        padding-left: 1ex;
+        }
+    .PopupSearchResultsPage .IEntry {
+        font-size: 10pt;
+        padding: 1px 5px;
+        }
+    .PopupSearchResultsPage .Opera9 .IEntry,
+    .FramedSearchResultsPage .Opera9 .IEntry {
+        text-align: left;
+        }
+    .FramedSearchResultsPage .IEntry {
+        padding: 0;
+        }
+
+    .ISubIndex {
+        padding-left: 3ex; padding-bottom: .5em }
+    .PopupSearchResultsPage .ISubIndex {
+        display: none;
+        }
+
+    /*  While it may cause some entries to look like links when they aren't, I found it's much easier to read the
+         index if everything's the same color.  */
+    .ISymbol {
+        font-weight: bold; color: #900000  }
+
+    .IndexPage .ISymbolPrefix,
+    .FramedIndexPage .ISymbolPrefix {
+        font-size: 12pt;
+        text-align: right;
+        color: #C47C7C;
+        background-color: #F8F8F8;
+        border-right: 3px solid #E0E0E0;
+        border-left: 1px solid #E0E0E0;
+        padding: 0 1px 0 2px;
+        }
+    .PopupSearchResultsPage .ISymbolPrefix,
+    .FramedSearchResultsPage .ISymbolPrefix {
+        color: #900000;
+        }
+    .PopupSearchResultsPage .ISymbolPrefix {
+        font-size: 10pt;
+        }
+
+    .IndexPage #IFirstSymbolPrefix,
+    .FramedIndexPage #IFirstSymbolPrefix {
+        border-top: 1px solid #E0E0E0;
+        }
+    .IndexPage #ILastSymbolPrefix,
+    .FramedIndexPage #ILastSymbolPrefix {
+        border-bottom: 1px solid #E0E0E0;
+        }
+    .IndexPage #IOnlySymbolPrefix,
+    .FramedIndexPage #IOnlySymbolPrefix {
+        border-top: 1px solid #E0E0E0;
+        border-bottom: 1px solid #E0E0E0;
+        }
+
+    a.IParent,
+    a.IFile {
+        display: block;
+        }
+
+    .PopupSearchResultsPage .SRStatus {
+        padding: 2px 5px;
+        font-size: 10pt;
+        font-style: italic;
+        }
+    .FramedSearchResultsPage .SRStatus {
+        font-size: 12pt;
+        font-style: italic;
+        }
+
+    .SRResult {
+        display: none;
+        }
+
+
+
+#Footer {
+    font-size: 8pt;
+    color: #989898;
+    text-align: right;
+    }
+
+#Footer p {
+    text-indent: 0;
+    margin-bottom: .5em;
+    }
+
+.ContentPage #Footer,
+.IndexPage #Footer {
+    text-align: right;
+    margin: 2px;
+    }
+
+.FramedMenuPage #Footer {
+    text-align: center;
+    margin: 5em 10px 10px 10px;
+    padding-top: 1em;
+    border-top: 1px solid #C8C8C8;
+    }
+
+    #Footer a:link,
+    #Footer a:hover,
+    #Footer a:visited { color: #989898 }
+    #Footer a:active { color: #A00000 }
+

Added: trunk/build/NaturalDocs-1.4/Styles/Small.css
===================================================================
--- trunk/build/NaturalDocs-1.4/Styles/Small.css	                        (rev 0)
+++ trunk/build/NaturalDocs-1.4/Styles/Small.css	2009-07-01 15:30:00 UTC (rev 4275)
@@ -0,0 +1,763 @@
+/*
+   IMPORTANT: If you're editing this file in the output directory of one of
+   your projects, your changes will be overwritten the next time you run
+   Natural Docs.  Instead, copy this file to your project directory, make your
+   changes, and you can use it with -s.  Even better would be to make a CSS
+   file in your project directory with only your changes, which you can then
+   use with -s [original style] [your changes].
+
+   On the other hand, if you're editing this file in the Natural Docs styles
+   directory, the changes will automatically be applied to all your projects
+   that use this style the next time Natural Docs is run on them.
+
+   This file is part of Natural Docs, which is Copyright (C) 2003-2008 Greg Valure
+   Natural Docs is licensed under the GPL
+*/
+
+body {
+    font: 8pt Verdana, Arial, sans-serif;
+    color: #000000;
+    margin: 0; padding: 0;
+    }
+
+.ContentPage,
+.IndexPage,
+.FramedMenuPage {
+    background-color: #E8E8E8;
+    }
+.FramedContentPage,
+.FramedIndexPage,
+.FramedSearchResultsPage,
+.PopupSearchResultsPage {
+    background-color: #FFFFFF;
+    }
+
+
+a:link,
+a:visited { color: #900000; text-decoration: none }
+a:hover { color: #900000; text-decoration: underline }
+a:active { color: #FF0000; text-decoration: underline }
+
+td {
+    vertical-align: top }
+
+img { border: 0;  }
+
+
+/*
+    Comment out this line to use web-style paragraphs (blank line between
+    paragraphs, no indent) instead of print-style paragraphs (no blank line,
+    indented.)
+*/
+p {
+    text-indent: 5ex; margin: 0 }
+
+
+/*  Opera doesn't break with just wbr, but will if you add this.  */
+.Opera wbr:after {
+	content: "\00200B";
+	}
+
+/*  Blockquotes are used as containers for things that may need to scroll.  */
+blockquote {
+    padding: 0;
+    margin: 0;
+    overflow: auto;
+    }
+
+
+.Firefox1 blockquote {
+    padding-bottom: .5em;
+    }
+
+/*  Turn off scrolling when printing.  */
+ at media print {
+    blockquote {
+        overflow: visible;
+        }
+    .IE blockquote {
+        width: auto;
+        }
+    }
+
+
+
+#Menu {
+    font-size: 8pt;
+    padding: 10px 0 0 0;
+    }
+.ContentPage #Menu,
+.IndexPage #Menu {
+    position: absolute;
+    top: 0;
+    left: 0;
+    width: 31ex;
+    overflow: hidden;
+    }
+.ContentPage .Firefox #Menu,
+.IndexPage .Firefox #Menu {
+    width: 27ex;
+    }
+
+
+    .MTitle {
+        font-size: 16pt; font-weight: bold; font-variant: small-caps;
+        text-align: center;
+        padding: 5px 10px 15px 10px;
+        border-bottom: 1px dotted #000000;
+        margin-bottom: 15px }
+
+    .MSubTitle {
+        font-size: 9pt; font-weight: normal; font-variant: normal;
+        margin-top: 1ex; margin-bottom: 5px }
+
+
+    .MEntry a:link,
+    .MEntry a:hover,
+    .MEntry a:visited { color: #606060; margin-right: 0 }
+    .MEntry a:active { color: #A00000; margin-right: 0 }
+
+
+    .MGroup {
+        font-variant: small-caps; font-weight: bold;
+        margin: 1em 0 1em 10px;
+        }
+
+    .MGroupContent {
+        font-variant: normal; font-weight: normal }
+
+    .MGroup a:link,
+    .MGroup a:hover,
+    .MGroup a:visited { color: #545454; margin-right: 10px }
+    .MGroup a:active { color: #A00000; margin-right: 10px }
+
+
+    .MFile,
+    .MText,
+    .MLink,
+    .MIndex {
+        padding: 1px 17px 2px 10px;
+        margin: .25em 0 .25em 0;
+        }
+
+    .MText {
+        font-size: 8pt; font-style: italic }
+
+    .MLink {
+        font-style: italic }
+
+    #MSelected {
+        color: #000000; background-color: #FFFFFF;
+        /*  Replace padding with border.  */
+        padding: 0 10px 0 10px;
+        border-width: 1px 2px 2px 0; border-style: solid; border-color: #000000;
+        margin-right: 5px;
+        }
+
+    /*  Close off the left side when its in a group.  */
+    .MGroup #MSelected {
+        padding-left: 9px; border-left-width: 1px }
+
+    /*  A treat for Mozilla users.  Blatantly non-standard.  Will be replaced with CSS 3 attributes when finalized/supported.  */
+    .Firefox #MSelected {
+        -moz-border-radius-topright: 10px;
+        -moz-border-radius-bottomright: 10px }
+    .Firefox .MGroup #MSelected {
+        -moz-border-radius-topleft: 10px;
+        -moz-border-radius-bottomleft: 10px }
+
+
+    #MSearchPanel {
+        padding: 0px 6px;
+        margin: .25em 0;
+        }
+
+
+    #MSearchField {
+        font: italic 8pt Verdana, sans-serif;
+        color: #606060;
+        background-color: #E8E8E8;
+        border: none;
+        padding: 2px 4px;
+        width: 100%;
+        }
+    /* Only Opera gets it right. */
+    .Firefox #MSearchField,
+    .IE #MSearchField,
+    .Safari #MSearchField {
+        width: 94%;
+        }
+    .Opera9 #MSearchField,
+    .Konqueror #MSearchField {
+        width: 97%;
+        }
+    .FramedMenuPage .Firefox #MSearchField,
+    .FramedMenuPage .Safari #MSearchField,
+    .FramedMenuPage .Konqueror #MSearchField {
+        width: 98%;
+        }
+
+    /* Firefox doesn't do this right in frames without #MSearchPanel added on.
+        It's presence doesn't hurt anything other browsers. */
+    #MSearchPanel.MSearchPanelInactive:hover #MSearchField {
+        background-color: #FFFFFF;
+        border: 1px solid #C0C0C0;
+        padding: 1px 3px;
+        }
+    .MSearchPanelActive #MSearchField {
+        background-color: #FFFFFF;
+        border: 1px solid #C0C0C0;
+        font-style: normal;
+        padding: 1px 3px;
+        }
+
+    #MSearchType {
+        visibility: hidden;
+        font: 8pt Verdana, sans-serif;
+        width: 98%;
+        padding: 0;
+        border: 1px solid #C0C0C0;
+        }
+    .MSearchPanelActive #MSearchType,
+    /*  As mentioned above, Firefox doesn't do this right in frames without #MSearchPanel added on. */
+    #MSearchPanel.MSearchPanelInactive:hover #MSearchType,
+    #MSearchType:focus {
+        visibility: visible;
+        color: #606060;
+        }
+    #MSearchType option#MSearchEverything {
+        font-weight: bold;
+        }
+
+    .Opera8 .MSearchPanelInactive:hover,
+    .Opera8 .MSearchPanelActive {
+        margin-left: -1px;
+        }
+
+
+    iframe#MSearchResults {
+        width: 60ex;
+        height: 15em;
+        }
+    #MSearchResultsWindow {
+        display: none;
+        position: absolute;
+        left: 0; top: 0;
+        border: 1px solid #000000;
+        background-color: #E8E8E8;
+        }
+    #MSearchResultsWindowClose {
+        font-weight: bold;
+        font-size: 8pt;
+        display: block;
+        padding: 2px 5px;
+        }
+    #MSearchResultsWindowClose:link,
+    #MSearchResultsWindowClose:visited {
+        color: #000000;
+        text-decoration: none;
+        }
+    #MSearchResultsWindowClose:active,
+    #MSearchResultsWindowClose:hover {
+        color: #800000;
+        text-decoration: none;
+        background-color: #F4F4F4;
+        }
+
+
+
+
+#Content {
+    padding-bottom: 15px;
+    }
+
+.ContentPage #Content {
+    border-width: 0 0 1px 1px;
+    border-style: solid;
+    border-color: #000000;
+    background-color: #FFFFFF;
+    font-size: 8pt;  /* To make 31ex match the menu's 31ex. */
+    margin-left: 31ex;
+    }
+.ContentPage .Firefox #Content {
+    margin-left: 27ex;
+    }
+
+
+
+    .CTopic {
+        font-size: 8pt;
+        margin-bottom: 3em;
+        }
+
+
+    .CTitle {
+        font-size: 11pt; font-weight: bold;
+        border-width: 0 0 1px 0; border-style: solid; border-color: #A0A0A0;
+        margin: 0 15px .5em 15px }
+
+    .CGroup .CTitle {
+        font-size: 16pt; font-variant: small-caps;
+        padding-left: 15px; padding-right: 15px;
+        border-width: 0 0 2px 0; border-color: #000000;
+        margin-left: 0; margin-right: 0 }
+
+    .CClass .CTitle,
+    .CInterface .CTitle,
+    .CDatabase .CTitle,
+    .CDatabaseTable .CTitle,
+    .CSection .CTitle {
+        font-size: 18pt;
+        color: #FFFFFF; background-color: #A0A0A0;
+        padding: 10px 15px 10px 15px;
+        border-width: 2px 0; border-color: #000000;
+        margin-left: 0; margin-right: 0 }
+
+    #MainTopic .CTitle {
+        font-size: 20pt;
+        color: #FFFFFF; background-color: #7070C0;
+        padding: 10px 15px 10px 15px;
+        border-width: 0 0 3px 0; border-color: #000000;
+        margin-left: 0; margin-right: 0 }
+
+    .CBody {
+        margin-left: 15px; margin-right: 15px }
+
+
+    .CToolTip {
+        position: absolute; visibility: hidden;
+        left: 0; top: 0;
+        background-color: #FFFFE0;
+        padding: 5px;
+        border-width: 1px 2px 2px 1px; border-style: solid; border-color: #000000;
+        font-size: 8pt;
+        }
+
+    .Opera .CToolTip {
+        max-width: 98%;
+        }
+
+    /*  Scrollbars would be useless.  */
+    .CToolTip blockquote {
+        overflow: hidden;
+        }
+    .IE6 .CToolTip blockquote {
+        overflow: visible;
+        }
+
+    .CHeading {
+        font-weight: bold; font-size: 9pt;
+        margin: 1.5em 0 .5em 0;
+        }
+
+    .CBody pre {
+        font: 8pt "Courier New", Courier, monospace;
+        margin: 1em 0;
+        }
+
+    .CBody ul {
+        /*  I don't know why CBody's margin doesn't apply, but it's consistent across browsers so whatever.
+             Reapply it here as padding.  */
+        padding-left: 15px; padding-right: 15px;
+        margin: .5em 5ex .5em 5ex;
+        }
+
+    .CDescriptionList {
+        margin: .5em 5ex 0 5ex }
+
+        .CDLEntry {
+            font: 8pt "Courier New", Courier, monospace; color: #808080;
+            padding-bottom: .25em;
+            white-space: nowrap }
+
+        .CDLDescription {
+            font-size: 8pt;  /*  For browsers that don't inherit correctly, like Opera 5.  */
+            padding-bottom: .5em; padding-left: 5ex }
+
+
+    .CTopic img {
+        text-align: center;
+        display: block;
+        margin: 1em auto;
+        }
+    .CImageCaption {
+        font-variant: small-caps;
+        font-size: 8pt;
+        color: #808080;
+        text-align: center;
+        position: relative;
+        top: 1em;
+        }
+
+    .CImageLink {
+        color: #808080;
+        font-style: italic;
+        }
+    a.CImageLink:link,
+    a.CImageLink:visited,
+    a.CImageLink:hover { color: #808080 }
+
+
+
+
+
+.Prototype {
+    font: 8pt "Courier New", Courier, monospace;
+    padding: 5px 3ex;
+    border-width: 1px; border-style: solid;
+    margin: 0 5ex 1.5em 5ex;
+    }
+
+    .Prototype td {
+        font-size: 8pt;
+        }
+
+    .PDefaultValue,
+    .PDefaultValuePrefix,
+    .PTypePrefix {
+        color: #8F8F8F;
+        }
+    .PTypePrefix {
+        text-align: right;
+        }
+    .PAfterParameters {
+        vertical-align: bottom;
+        }
+
+    .IE .Prototype table {
+        padding: 0;
+        }
+
+    .CFunction .Prototype {
+        background-color: #F4F4F4; border-color: #D0D0D0 }
+    .CProperty .Prototype {
+        background-color: #F4F4FF; border-color: #C0C0E8 }
+    .CVariable .Prototype {
+        background-color: #FFFFF0; border-color: #E0E0A0 }
+
+    .CClass .Prototype {
+        border-width: 1px 2px 2px 1px; border-style: solid; border-color: #A0A0A0;
+        background-color: #F4F4F4;
+        }
+    .CInterface .Prototype {
+        border-width: 1px 2px 2px 1px; border-style: solid; border-color: #A0A0D0;
+        background-color: #F4F4FF;
+        }
+
+    .CDatabaseIndex .Prototype,
+    .CConstant .Prototype {
+        background-color: #D0D0D0; border-color: #000000 }
+    .CType .Prototype,
+    .CEnumeration .Prototype {
+        background-color: #FAF0F0; border-color: #E0B0B0;
+        }
+    .CDatabaseTrigger .Prototype,
+    .CEvent .Prototype,
+    .CDelegate .Prototype {
+        background-color: #F0FCF0; border-color: #B8E4B8 }
+
+    .CToolTip .Prototype {
+        margin: 0 0 .5em 0;
+        white-space: nowrap;
+        }
+
+
+
+
+
+.Summary {
+    margin: 1.5em 5ex 0 5ex }
+
+    .STitle {
+        font-size: 11pt; font-weight: bold;
+        margin-bottom: .5em }
+
+
+    .SBorder {
+        background-color: #FFFFF0;
+        padding: 15px;
+        border: 1px solid #C0C060 }
+
+    /* In a frame IE 6 will make them too long unless you set the width to 100%.  Without frames it will be correct without a width
+        or slightly too long (but not enough to scroll) with a width.  This arbitrary weirdness simply astounds me.  IE 7 has the same
+        problem with frames, haven't tested it without.  */
+    .FramedContentPage .IE .SBorder {
+        width: 100% }
+
+    /*  A treat for Mozilla users.  Blatantly non-standard.  Will be replaced with CSS 3 attributes when finalized/supported.  */
+    .Firefox .SBorder {
+        -moz-border-radius: 20px }
+
+
+    .STable {
+        font-size: 8pt; width: 100% }
+
+    .SEntry {
+        width: 30% }
+    .SDescription {
+        width: 70% }
+
+
+    .SMarked {
+        background-color: #F8F8D8 }
+
+    .SDescription { padding-left: 2ex }
+    .SIndent1 .SEntry { padding-left: 1.5ex }   .SIndent1 .SDescription { padding-left: 3.5ex }
+    .SIndent2 .SEntry { padding-left: 3.0ex }   .SIndent2 .SDescription { padding-left: 5.0ex }
+    .SIndent3 .SEntry { padding-left: 4.5ex }   .SIndent3 .SDescription { padding-left: 6.5ex }
+    .SIndent4 .SEntry { padding-left: 6.0ex }   .SIndent4 .SDescription { padding-left: 8.0ex }
+    .SIndent5 .SEntry { padding-left: 7.5ex }   .SIndent5 .SDescription { padding-left: 9.5ex }
+
+    .SDescription a { color: #800000}
+    .SDescription a:active { color: #A00000 }
+
+    .SGroup td {
+        padding-top: .5em; padding-bottom: .25em }
+
+    .SGroup .SEntry {
+        font-weight: bold; font-variant: small-caps }
+
+    .SGroup .SEntry a { color: #800000 }
+    .SGroup .SEntry a:active { color: #F00000 }
+
+
+    .SMain td,
+    .SClass td,
+    .SDatabase td,
+    .SDatabaseTable td,
+    .SSection td {
+        font-size: 10pt;
+        padding-bottom: .25em }
+
+    .SClass td,
+    .SDatabase td,
+    .SDatabaseTable td,
+    .SSection td {
+        padding-top: 1em }
+
+    .SMain .SEntry,
+    .SClass .SEntry,
+    .SDatabase .SEntry,
+    .SDatabaseTable .SEntry,
+    .SSection .SEntry {
+        font-weight: bold;
+        }
+
+    .SMain .SEntry a,
+    .SClass .SEntry a,
+    .SDatabase .SEntry a,
+    .SDatabaseTable .SEntry a,
+    .SSection .SEntry a { color: #000000 }
+
+    .SMain .SEntry a:active,
+    .SClass .SEntry a:active,
+    .SDatabase .SEntry a:active,
+    .SDatabaseTable .SEntry a:active,
+    .SSection .SEntry a:active { color: #A00000 }
+
+
+
+
+
+.ClassHierarchy {
+    margin: 0 15px 1em 15px }
+
+    .CHEntry {
+        border-width: 1px 2px 2px 1px; border-style: solid; border-color: #A0A0A0;
+        margin-bottom: 3px;
+        padding: 2px 2ex;
+        font-size: 8pt;
+        background-color: #F4F4F4; color: #606060;
+        }
+
+    .Firefox .CHEntry {
+        -moz-border-radius: 4px;
+        }
+
+    .CHCurrent .CHEntry {
+        font-weight: bold;
+        border-color: #000000;
+        color: #000000;
+        }
+
+    .CHChildNote .CHEntry {
+        font-style: italic;
+        font-size: 8pt;
+        }
+
+    .CHIndent {
+        margin-left: 3ex;
+        }
+
+    .CHEntry a:link,
+    .CHEntry a:visited,
+    .CHEntry a:hover {
+        color: #606060;
+        }
+    .CHEntry a:active {
+        color: #800000;
+        }
+
+
+
+
+
+#Index {
+    background-color: #FFFFFF;
+    }
+
+/*  As opposed to .PopupSearchResultsPage #Index  */
+.IndexPage #Index,
+.FramedIndexPage #Index,
+.FramedSearchResultsPage #Index {
+    padding: 15px;
+    }
+
+.IndexPage #Index {
+    border-width: 0 0 1px 1px;
+    border-style: solid;
+    border-color: #000000;
+    font-size: 8pt;  /* To make 27ex match the menu's 27ex. */
+    margin-left: 27ex;
+    }
+
+
+    .IPageTitle {
+        font-size: 20pt; font-weight: bold;
+        color: #FFFFFF; background-color: #7070C0;
+        padding: 10px 15px 10px 15px;
+        border-width: 0 0 3px 0; border-color: #000000; border-style: solid;
+        margin: -15px -15px 0 -15px }
+
+    .FramedSearchResultsPage .IPageTitle {
+        margin-bottom: 15px;
+        }
+
+    .INavigationBar {
+        text-align: center;
+        background-color: #FFFFF0;
+        padding: 5px;
+        border-bottom: solid 1px black;
+        margin: 0 -15px 15px -15px;
+        }
+
+    .INavigationBar a {
+        font-weight: bold }
+
+    .IHeading {
+        font-size: 14pt; font-weight: bold;
+        padding: 2.5em 0 .5em 0;
+        text-align: center;
+        width: 3.5ex;
+        }
+    #IFirstHeading {
+        padding-top: 0;
+        }
+
+    .IEntry {
+        padding-left: 1ex;
+        }
+    .PopupSearchResultsPage .IEntry {
+        font-size: 8pt;
+        padding: 1px 5px;
+        }
+    .PopupSearchResultsPage .Opera9 .IEntry,
+    .FramedSearchResultsPage .Opera9 .IEntry {
+        text-align: left;
+        }
+    .FramedSearchResultsPage .IEntry {
+        padding: 0;
+        }
+
+    .ISubIndex {
+        padding-left: 3ex; padding-bottom: .5em }
+    .PopupSearchResultsPage .ISubIndex {
+        display: none;
+        }
+
+    /*  While it may cause some entries to look like links when they aren't, I found it's much easier to read the
+         index if everything's the same color.  */
+    .ISymbol {
+        font-weight: bold; color: #900000  }
+
+    .IndexPage .ISymbolPrefix,
+    .FramedIndexPage .ISymbolPrefix {
+        text-align: right;
+        color: #C47C7C;
+        background-color: #F8F8F8;
+        border-right: 3px solid #E0E0E0;
+        border-left: 1px solid #E0E0E0;
+        padding: 0 1px 0 2px;
+        }
+    .PopupSearchResultsPage .ISymbolPrefix,
+    .FramedSearchResultsPage .ISymbolPrefix {
+        color: #900000;
+        }
+    .PopupSearchResultsPage .ISymbolPrefix {
+        font-size: 8pt;
+        }
+
+    .IndexPage #IFirstSymbolPrefix,
+    .FramedIndexPage #IFirstSymbolPrefix {
+        border-top: 1px solid #E0E0E0;
+        }
+    .IndexPage #ILastSymbolPrefix,
+    .FramedIndexPage #ILastSymbolPrefix {
+        border-bottom: 1px solid #E0E0E0;
+        }
+    .IndexPage #IOnlySymbolPrefix,
+    .FramedIndexPage #IOnlySymbolPrefix {
+        border-top: 1px solid #E0E0E0;
+        border-bottom: 1px solid #E0E0E0;
+        }
+
+    a.IParent,
+    a.IFile {
+        display: block;
+        }
+
+    .PopupSearchResultsPage .SRStatus {
+        padding: 2px 5px;
+        font-size: 8pt;
+        font-style: italic;
+        }
+    .FramedSearchResultsPage .SRStatus {
+        font-size: 8pt;
+        font-style: italic;
+        }
+
+    .SRResult {
+        display: none;
+        }
+
+
+
+#Footer {
+    font-size: 8pt;
+    color: #989898;
+    text-align: right;
+    }
+
+#Footer p {
+    text-indent: 0;
+    margin-bottom: .5em;
+    }
+
+.ContentPage #Footer,
+.IndexPage #Footer {
+    text-align: right;
+    margin: 2px;
+    }
+
+.FramedMenuPage #Footer {
+    text-align: center;
+    margin: 5em 10px 10px 10px;
+    padding-top: 1em;
+    border-top: 1px solid #C8C8C8;
+    }
+
+    #Footer a:link,
+    #Footer a:hover,
+    #Footer a:visited { color: #989898 }
+    #Footer a:active { color: #A00000 }
+



More information about the Mapbender_commits mailing list