#!/usr/bin/env perl
# -*- cperl -*-
#
# gtk-doc - GTK DocBook documentation generator.
# Copyright (C) 1998  Damon Chaplin
#               2007,2008,2009  Stefan Kost
#
# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#

#############################################################################
# Script      : gtkdoc-mkdb
# Description : This creates the DocBook files from the edited templates.
#############################################################################

use warnings;
use strict;
use Getopt::Long;

push @INC, 'FIXMESTAGINGDIR/x86_64-linux/usr/share/gtk-doc/data';
require "gtkdoc-common.pl";

# Options

# name of documentation module
my $MODULE;
my $TMPL_DIR;
my $DB_OUTPUT_DIR;
my @SOURCE_DIRS;
my $SOURCE_SUFFIXES = "";
my $IGNORE_FILES = "";
my $PRINT_VERSION;
my $PRINT_HELP;
my $MAIN_SGML_FILE;
my $EXPAND_CONTENT_FILES = "";
my $INLINE_MARKUP_MODE;
my $DEFAULT_STABILITY;
my $DEFAULT_INCLUDES;
my $OUTPUT_FORMAT;
my $NAME_SPACE = "";
my $OUTPUT_ALL_SYMBOLS;
my $OUTPUT_SYMBOLS_WITHOUT_SINCE;
my $ROOT_DIR = ".";
my $OBJECT_TREE_FILE;
my $INTERFACES_FILE;
my $PREREQUISITES_FILE;
my $SIGNALS_FILE;
my $ARGS_FILE;

# These global arrays store information on signals. Each signal has an entry
# in each of these arrays at the same index, like a multi-dimensional array.
my @SignalObjects;        # The GtkObject which emits the signal.
my @SignalNames;        # The signal name.
my @SignalReturns;        # The return type.
my @SignalFlags;        # Flags for the signal
my @SignalPrototypes;        # The rest of the prototype of the signal handler.

# These global arrays store information on Args. Each Arg has an entry
# in each of these arrays at the same index, like a multi-dimensional array.
my @ArgObjects;                # The GtkObject which has the Arg.
my @ArgNames;                # The Arg name.
my @ArgTypes;                # The Arg type - gint, GtkArrowType etc.
my @ArgFlags;                # How the Arg can be used - readable/writable etc.
my @ArgNicks;                # The nickname of the Arg.
my @ArgBlurbs;          # Docstring of the Arg.
my @ArgDefaults;        # Default value of the Arg.
my @ArgRanges;                # The range of the Arg type
# These global hashes store declaration info keyed on a symbol name.
my %Declarations;
my %DeclarationTypes;
my %DeclarationConditional;
my %DeclarationOutput;
my %Deprecated;
my %Since;
my %StabilityLevel;
my %StructHasTypedef;

# These global hashes store the existing documentation.
my %SymbolDocs;
my %SymbolTypes;
my %SymbolParams;
my %SymbolSourceFile;
my %SymbolSourceLine;
my %SymbolAnnotations;

# These global hashes store documentation scanned from the source files.
my %SourceSymbolDocs;
my %SourceSymbolParams;
my %SourceSymbolSourceFile;
my %SourceSymbolSourceLine;

# all documentation goes in here, so we can do coverage analysis
my %AllSymbols;
my %AllIncompleteSymbols;
my %AllUnusedSymbols;
my %AllDocumentedSymbols;

# Undeclared yet documented symbols
my %UndeclaredSymbols;

# These global arrays store GObject, subclasses and the hierarchy (also of
# non-object derived types).
my @Objects;
my @ObjectLevels;
my %ObjectRoots;

my %Interfaces;
my %Prerequisites;

# holds the symbols which are mentioned in $MODULE-sections.txt and in which
# section they are defined
my %KnownSymbols;
my %SymbolSection;
my %SymbolSectionId;

# collects index entries
my %IndexEntriesFull;
my %IndexEntriesSince;
my %IndexEntriesDeprecated;

# Standard C preprocessor directives, which we ignore for '#' abbreviations.
my %PreProcessorDirectives = (
    'assert' => 1,
    'define' => 1,
    'elif' => 1,
    'else' => 1,
    'endif' => 1,
    'error' => 1,
    'if' => 1,
    'ifdef' => 1,
    'ifndef' => 1,
    'include' => 1,
    'line' => 1,
    'pragma' => 1,
    'unassert' => 1,
    'undef' => 1,
    'warning' => 1
);

# remember used annotation (to write minimal glossary)
my %AnnotationsUsed;

my %AnnotationDefinition = (
    # the GObjectIntrospection annotations are defined at:
    # https://live.gnome.org/GObjectIntrospection/Annotations
    'allow-none' => "NULL is OK, both for passing and for returning.",
    'nullable' => "NULL may be passed as the value in, out, in-out; or as a return value.",
    'not nullable' => "NULL must not be passed as the value in, out, in-out; or as a return value.",
    'optional' => "NULL may be passed instead of a pointer to a location.",
    'array' => "Parameter points to an array of items.",
    'attribute' => "Deprecated free-form custom annotation, replaced by (attributes) annotation.",
    'attributes' => "Free-form key-value pairs.",
    'closure' => "This parameter is a 'user_data', for callbacks; many bindings can pass NULL here.",
    'constructor' => "This symbol is a constructor, not a static method.",
    'destroy' => "This parameter is a 'destroy_data', for callbacks.",
    'default' => "Default parameter value (for in case the <acronym>shadows</acronym>-to function has less parameters).",
    'element-type' => "Generics and defining elements of containers and arrays.",
    'error-domains' => "Typed errors. Similar to throws in Java.",
    'foreign' => "This is a foreign struct.",
    'get-value-func' => "The specified function is used to convert a struct from a GValue, must be a GTypeInstance.",
    'in' => "Parameter for input. Default is <acronym>transfer none</acronym>.",
    'inout' => "Parameter for input and for returning results. Default is <acronym>transfer full</acronym>.",
    'in-out' => "Parameter for input and for returning results. Default is <acronym>transfer full</acronym>.",
    'method' => "This is a method",
    'not-error' => "A GError parameter is not to be handled like a normal GError.",
    'out' => "Parameter for returning results. Default is <acronym>transfer full</acronym>.",
    'out caller-allocates' => "Out parameter, where caller must allocate storage.",
    'out callee-allocates' => "Out parameter, where caller must allocate storage.",
    'ref-func' => "The specified function is used to ref a struct, must be a GTypeInstance.",
    'rename-to' => "Rename the original symbol's name to SYMBOL.",
    'scope call' => "The callback is valid only during the call to the method.",
    'scope async' => "The callback is valid until first called.",
    'scope notified' => "The callback is valid until the GDestroyNotify argument is called.",
    'set-value-func' => "The specified function is used to convert from a struct to a GValue, must be a GTypeInstance.",
    'skip' => "Exposed in C code, not necessarily available in other languages.",
    'transfer container' => "Free data container after the code is done.",
    'transfer floating' => "Alias for <acronym>transfer none</acronym>, used for objects with floating refs.",
    'transfer full' => "Free data after the code is done.",
    'transfer none' => "Don't free data after the code is done.",
    'type' => "Override the parsed C type with given type.",
    'unref-func' => "The specified function is used to unref a struct, must be a GTypeInstance.",
    'virtual' => "This is the invoker for a virtual method.",
    'value' => "The specified value overrides the evaluated value of the constant.",
    # Stability Level definition
    # https://bugzilla.gnome.org/show_bug.cgi?id=170860
    'Stable' => <<EOF,
The intention of a Stable interface is to enable arbitrary third parties to
develop applications to these interfaces, release them, and have confidence that
they will run on all minor releases of the product (after the one in which the
interface was introduced, and within the same major release). Even at a major
release, incompatible changes are expected to be rare, and to have strong
justifications.
EOF
    'Unstable' => <<EOF,
Unstable interfaces are experimental or transitional. They are typically used to
give outside developers early access to new or rapidly changing technology, or
to provide an interim solution to a problem where a more general solution is
anticipated. No claims are made about either source or binary compatibility from
one minor release to the next.

The Unstable interface level is a warning that these interfaces are  subject to
change without warning and should not be used in unbundled products.

Given such caveats, customer impact need not be a factor when considering
incompatible changes to an Unstable interface in a major or minor release.
Nonetheless, when such changes are introduced, the changes should still be
mentioned in the release notes for the affected release.
EOF
    'Private' => <<EOF
An interface that can be used within the GNOME stack itself, but that is not
documented for end-users.  Such functions should only be used in specified and
documented ways.
EOF
);

# Elements to consider non-block items in MarkDown parsing
my %MD_TEXT_LEVEL_ELEMENTS = ( "literal" => 1,
                               "emphasis" => 1,
                               "envar" => 1,
                               "filename" => 1,
                               "firstterm" => 1,
                               "footnote" => 1,
                               "function" => 1,
                               "manvolnum" => 1,
                               "option" => 1,
                               "replaceable" => 1,
                               "structfield" => 1,
                               "structname" => 1,
                               "title" => 1,
                               "varname" => 1 );
my %MD_ESCAPABLE_CHARS = ( "\\" => 1,
                           "`" => 1,
                           "*" => 1,
                           "_" => 1,
                           "{" => 1,
                           "}" => 1,
                           "[" => 1,
                           "]" => 1,
                           "(" => 1,
                           ")" => 1,
                           ">" => 1,
                           "#" => 1,
                           "+" => 1,
                           "-" => 1,
                           "." => 1,
                           "!" => 1 );
my %MD_GTK_ESCAPABLE_CHARS = ( "@" => 1,
                               "%" => 1 );

# Function and other declaration output settings.
my $RETURN_TYPE_FIELD_WIDTH = 20;
my $SYMBOL_FIELD_WIDTH = 36;
my $MAX_SYMBOL_FIELD_WIDTH = 40;
my $SIGNAL_FIELD_WIDTH = 16;
my $PARAM_FIELD_COUNT = 2;

# XML, SGML formatting helper
my $doctype_header;


run() unless caller; # Run program unless loaded as a module


sub run {
    my %optctl = ('module' => \$MODULE,
                  'source-dir' => \@SOURCE_DIRS,
                  'source-suffixes' => \$SOURCE_SUFFIXES,
                  'ignore-files' => \$IGNORE_FILES,
                  'output-dir' => \$DB_OUTPUT_DIR,
                  'tmpl-dir' => \$TMPL_DIR,
                  'version' => \$PRINT_VERSION,
                  'help' => \$PRINT_HELP,
                  'main-sgml-file' => \$MAIN_SGML_FILE,
                  'expand-content-files' => \$EXPAND_CONTENT_FILES,
                  'sgml-mode' => \$INLINE_MARKUP_MODE,
                  'xml-mode' => \$INLINE_MARKUP_MODE,
                  'default-stability' => \$DEFAULT_STABILITY,
                  'default-includes' => \$DEFAULT_INCLUDES,
                  'output-format' => \$OUTPUT_FORMAT,
                  'name-space' => \$NAME_SPACE,
                  'outputallsymbols' => \$OUTPUT_ALL_SYMBOLS,
                  'outputsymbolswithoutsince' => \$OUTPUT_SYMBOLS_WITHOUT_SINCE
                  );
    GetOptions(\%optctl, "module=s", "source-dir:s", "source-suffixes:s",
        "ignore-files:s", "output-dir:s", "tmpl-dir:s", "version", 
        "outputallsymbols", "outputsymbolswithoutsince",
        "expand-content-files:s", "main-sgml-file:s", "extra-db-files:s", "help",
        "sgml-mode", "xml-mode", "default-stability:s", "default-includes:s",
        "output-format:s", "name-space:s");
    
    if ($PRINT_VERSION) {
        print "1.25\n";
        exit 0;
    }
    
    if (!$MODULE) {
        $PRINT_HELP = 1;
    }
    
    if ($DEFAULT_STABILITY && $DEFAULT_STABILITY ne "Stable"
        && $DEFAULT_STABILITY ne "Private" && $DEFAULT_STABILITY ne "Unstable") {
        $PRINT_HELP = 1;
    }
    
    if ($PRINT_HELP) {
        print <<EOF;
gtkdoc-mkdb version 1.25 - generate docbook files

--module=MODULE_NAME       Name of the doc module being parsed
--source-dir=DIRNAME       Directories which contain inline reference material
--source-suffixes=SUFFIXES Suffixes of source files to scan, comma-separated
--ignore-files=FILES       A space-separated list of header files/dirs not to
                           scan
--output-dir=DIRNAME       Directory to put the generated DocBook files in
--tmpl-dir=DIRNAME         Directory in which template files may be found
--main-sgml-file=FILE      File containing the toplevel DocBook file.
--expand-content-files=FILES Extra DocBook files to expand abbreviations in.
--output-format=FORMAT     Format to use for the generated docbook, XML or SGML.
--{xml,sgml}-mode          Allow DocBook markup in inline documentation.
--default-stability=LEVEL  Specify default stability Level. Valid values are
                           Stable, Unstable, or Private.
--default-includes=FILENAMES Specify default includes for section Synopsis
--name-space=NS            Omit namespace in index.
--version                  Print the version of this program
--help                     Print this help
EOF
        exit 0;
    }
  
    #(" ignore files: [$IGNORE_FILES]\n");
        
    # check output format
    if (! defined($OUTPUT_FORMAT) || ($OUTPUT_FORMAT eq "")) {
        $OUTPUT_FORMAT = "xml";    
    } else {
        $OUTPUT_FORMAT = lc($OUTPUT_FORMAT);
    }
    if ($OUTPUT_FORMAT ne "xml") {
        die "Invalid format '$OUTPUT_FORMAT' passed to --output.format"
    }
    
    if (!$MAIN_SGML_FILE) {
        # backwards compatibility
        if (-e "${MODULE}-docs.sgml") {
            $MAIN_SGML_FILE = "${MODULE}-docs.sgml";
        } else {
            $MAIN_SGML_FILE = "${MODULE}-docs.xml";
        }
    }

    # extract docbook header or define default
    if (-e $MAIN_SGML_FILE) {
        open(INPUT, "<$MAIN_SGML_FILE") || die "Can't open $MAIN_SGML_FILE";
        $doctype_header = "";
        while (<INPUT>) {
            if (/^\s*<(book|chapter|article)/) {
                # check that the top-level tagSYSTEM or the doctype decl contain the xinclude namespace decl
                if (($_ !~ m/http:\/\/www.w3.org\/200[13]\/XInclude/) && ($doctype_header !~ m/http:\/\/www.w3.org\/200[13]\/XInclude/m)) {
                    $doctype_header = "";
                }
                last;
            }
            # if there are SYSTEM ENTITIES here, we should prepend "../" to the path
            # FIXME: not sure if we can do this now, as people already work-around the problem
            # s#<!ENTITY % ([a-zA-Z-]+) SYSTEM \"([^/][a-zA-Z./]+)\">#<!ENTITY % $1 SYSTEM \"../$2\">#;
            s#<!ENTITY % gtkdocentities SYSTEM \"([^"]*)\">#<!ENTITY % gtkdocentities SYSTEM \"../$1\">#;
            $doctype_header .= $_;
        }
        close(INPUT);
    } else {
        $doctype_header = <<EOF;
<?xml version="1.0"?>
<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.3//EN"
               "http://www.oasis-open.org/docbook/xml/4.3/docbookx.dtd"
[
  <!ENTITY % local.common.attrib "xmlns:xi  CDATA  #FIXED 'http://www.w3.org/2003/XInclude'">
  <!ENTITY % gtkdocentities SYSTEM "../xml/gtkdocentities.ent">
  %gtkdocentities;
]>
EOF
    }
    chomp($doctype_header);
    
    # All the files are written in subdirectories beneath here.
    $TMPL_DIR = $TMPL_DIR ? $TMPL_DIR : "$ROOT_DIR/tmpl";
    
    # This is where we put all the DocBook output.
    $DB_OUTPUT_DIR = $DB_OUTPUT_DIR ? $DB_OUTPUT_DIR : "$ROOT_DIR/xml";
  
    # This file contains the object hierarchy.
    $OBJECT_TREE_FILE = "$ROOT_DIR/$MODULE.hierarchy";
  
    # This file contains the interfaces.
    $INTERFACES_FILE = "$ROOT_DIR/$MODULE.interfaces";
    
    # This file contains the prerequisites.
    $PREREQUISITES_FILE = "$ROOT_DIR/$MODULE.prerequisites";
    
    # This file contains signal arguments and names.
    $SIGNALS_FILE = "$ROOT_DIR/$MODULE.signals";
    
    # The file containing Arg information.
    $ARGS_FILE = "$ROOT_DIR/$MODULE.args";
  
    # Create the root DocBook output directory if it doens't exist.
    if (! -e $DB_OUTPUT_DIR) {
        mkdir ("$DB_OUTPUT_DIR", 0777)
            || die "Can't create directory: $DB_OUTPUT_DIR";
    }
    
    &ReadKnownSymbols ("$ROOT_DIR/$MODULE-sections.txt");
    &ReadSignalsFile ($SIGNALS_FILE);
    &ReadArgsFile ($ARGS_FILE);
    &ReadObjectHierarchy;
    &ReadInterfaces;
    &ReadPrerequisites;
    
    &ReadDeclarationsFile ("$ROOT_DIR/$MODULE-decl.txt", 0);
    if (-f "$ROOT_DIR/$MODULE-overrides.txt") {
        &ReadDeclarationsFile ("$ROOT_DIR/$MODULE-overrides.txt", 1);
    }
    
    for my $dir (@SOURCE_DIRS) {
        &ReadSourceDocumentation ($dir);
    }
    
    my $changed = &OutputDB ("$ROOT_DIR/$MODULE-sections.txt");
    
    # If any of the DocBook files have changed, update the timestamp file (so
    # it can be used for Makefile dependencies).
    if ($changed || ! -e "$ROOT_DIR/sgml.stamp") {
    
        # try to detect the common prefix
        # GtkWidget, GTK_WIDGET, gtk_widget -> gtk
        if ($NAME_SPACE eq "") {
            $NAME_SPACE="";
            my $pos=0;
            my $ratio=0.0;
            do {
                my %prefix;
                my $letter="";
                foreach my $symbol (keys(%IndexEntriesFull)) {
                    if(($NAME_SPACE eq "") || $symbol =~ /^$NAME_SPACE/i) {
                        if (length($symbol)>$pos) {
                            $letter=substr($symbol,$pos,1);
                            # stop prefix scanning
                            if ($letter eq "_") {
                                # stop on "_"
                                last;
                            }
                            # Should we also stop on a uppercase char, if last was lowercase
                            #   GtkWidget, if we have the 'W' and had the 't' before
                            # or should we count upper and lowercase, and stop one 2nd uppercase, if we already had a lowercase
                            #   GtkWidget, the 'W' would be the 2nd uppercase and with 't','k' we had lowercase chars before
                            # need to recound each time as this is per symbol
                            $prefix{uc($letter)}++;
                        }
                    }
                }
                if ($letter ne "" && $letter ne "_") {
                    my $maxletter="";
                    my $maxsymbols=0;
                    foreach $letter (keys(%prefix)) {
                        #print "$letter: $prefix{$letter}.\n";
                        if ($prefix{$letter}>$maxsymbols) {
                            $maxletter=$letter;
                            $maxsymbols=$prefix{$letter};
                        }
                    }
                    $ratio = scalar(keys(%IndexEntriesFull)) / $prefix{$maxletter};
                    #print "most symbols start with $maxletter, that is ". (100 * $ratio) ." %\n";
                    if ($ratio > 0.9) {
                        # do another round
                        $NAME_SPACE .= $maxletter;
                    }
                    $pos++;
                }
                else {
                    $ratio=0.0;
                }
            } while ($ratio > 0.9);
            #print "most symbols start with $NAME_SPACE\n";
        }
    
        &OutputIndexFull;
        &OutputDeprecatedIndex;
        &OutputSinceIndexes;
        &OutputAnnotationGlossary;
    
        open (TIMESTAMP, ">$ROOT_DIR/sgml.stamp")
            || die "Can't create $ROOT_DIR/sgml.stamp: $!";
        print (TIMESTAMP "timestamp");
        close (TIMESTAMP);
    }
}

#############################################################################
# Function    : OutputObjectList
# Description : This outputs the alphabetical list of objects, in a columned
#                table.
#               FIXME: Currently this also outputs ancestor objects
#                which may not actually be in this module.
# Arguments   : none
#############################################################################

sub OutputObjectList {
    my $cols = 3;

    # FIXME: use .xml
    # my $old_object_index = "$DB_OUTPUT_DIR/object_index.xml";
    my $old_object_index = "$DB_OUTPUT_DIR/object_index.sgml";
    my $new_object_index = "$DB_OUTPUT_DIR/object_index.new";

    open (OUTPUT, ">$new_object_index")
        || die "Can't create $new_object_index: $!";

    print (OUTPUT <<EOF);
${\( MakeDocHeader ("informaltable") )}
<informaltable pgwide="1" frame="none">
<tgroup cols="$cols">
<colspec colwidth="1*"/>
<colspec colwidth="1*"/>
<colspec colwidth="1*"/>
<tbody>
EOF

    my $count = 0;
    my $object;
    foreach $object (sort (@Objects)) {
        my $xref = &MakeXRef ($object);
        if ($count % $cols == 0) { print (OUTPUT "<row>\n"); }
        print (OUTPUT "<entry>$xref</entry>\n");
        if ($count % $cols == ($cols - 1)) { print (OUTPUT "</row>\n"); }
        $count++;
    }
    if ($count == 0) {
        # emit an empty row, since empty tables are invalid
        print (OUTPUT "<row><entry> </entry></row>\n");
    }
    else {
        if ($count % $cols > 0) {
            print (OUTPUT "</row>\n");
        }
    }

    print (OUTPUT <<EOF);
</tbody></tgroup></informaltable>
EOF
    close (OUTPUT);

    &UpdateFileIfChanged ($old_object_index, $new_object_index, 0);
}

#############################################################################
# Function    : TrimTextBlock
# Description : Trims extra whitespace. Empty lines inside a block are
#                preserved.
# Arguments   : $desc - the text block to trim. May contain newlines.
#############################################################################

sub TrimTextBlock {
  my ($desc) = @_;
  
  # strip leading spaces on the block
  $desc =~ s/^\s+//s;
  # strip trailing spaces on every line
  $desc =~ s/\s+$/\n/mg;
  
  return $desc;
}


#############################################################################
# Function    : OutputDB
# Description : This collects the output for each section of the docs, and
#                outputs each file when the end of the section is found.
# Arguments   : $file - the $MODULE-sections.txt file which contains all of
#                the functions/macros/structs etc. being documented, organised
#                into sections and subsections.
#############################################################################

sub OutputDB {
    my ($file) = @_;

    #("Reading: $file\n");
    open (INPUT, $file)
        || die "Can't open $file: $!";
    my $filename = "";
    my $book_top = "";
    my $book_bottom = "";
    my $includes = (defined $DEFAULT_INCLUDES) ? $DEFAULT_INCLUDES : "";
    my $section_includes = "";
    my $in_section = 0;
    my $title = "";
    my $section_id = "";
    my $subsection = "";
    my $num_symbols;
    my $changed = 0;
    my $functions_synop = "";
    my $other_synop = "";
    my $functions_details = "";
    my $other_details = "";
    my $signals_synop = "";
    my $signals_desc = "";
    my $args_synop = "";
    my $child_args_synop = "";
    my $style_args_synop = "";
    my $args_desc = "";
    my $child_args_desc = "";
    my $style_args_desc = "";
    my $hierarchy_str = "";
    my @hierarchy = ();
    my $interfaces = "";
    my $implementations = "";
    my $prerequisites = "";
    my $derived = "";
    my @file_objects = ();
    my %templates = ();
    my %symbol_def_line = ();

    # merge the source docs, in case there are no templates
    &MergeSourceDocumentation;

    while (<INPUT>) {
        if (m/^#/) {
            next;

        } elsif (m/^<SECTION>/) {
            $num_symbols = 0;
            $in_section = 1;
            @file_objects = ();
            %symbol_def_line = ();

        } elsif (m/^<SUBSECTION\s*(.*)>/i) {
            $other_synop .= "\n";
            $functions_synop .= "\n";
            $subsection = $1;

        } elsif (m/^<SUBSECTION>/) {

        } elsif (m/^<TITLE>(.*)<\/TITLE>/) {
            $title = $1;
            #("Section: $title\n");

            # We don't want warnings if object & class structs aren't used.
            $DeclarationOutput{$title} = 1;
            $DeclarationOutput{"${title}Class"} = 1;
            $DeclarationOutput{"${title}Iface"} = 1;
            $DeclarationOutput{"${title}Interface"} = 1;

        } elsif (m/^<FILE>(.*)<\/FILE>/) {
            $filename = $1;
            if (! defined $templates{$filename}) {
               if (&ReadTemplateFile ("$TMPL_DIR/$filename", 1)) {
                   &MergeSourceDocumentation;
                   $templates{$filename}=$.;
               }
            } else {
                &LogWarning ($file, $., "Double <FILE>$filename</FILE> entry. ".
                    "Previous occurrence on line ".$templates{$filename}.".");
            }
            if (($title eq "") and (defined $SourceSymbolDocs{"$TMPL_DIR/$filename:Title"})) {
                $title = $SourceSymbolDocs{"$TMPL_DIR/$filename:Title"};
                 # Remove trailing blanks
                $title =~ s/\s+$//;
           }

        } elsif (m/^<INCLUDE>(.*)<\/INCLUDE>/) {
            if ($in_section) {
                $section_includes = $1;
            } else {
                if (defined $DEFAULT_INCLUDES) {
                    &LogWarning ($file, $., "Default <INCLUDE> being overridden by command line option.");
                }
                else {
                    $includes = $1;
                }
            }

        } elsif (m/^<\/SECTION>/) {
            #("End of section: $title\n");
            if ($num_symbols > 0) {
                # collect documents
                $book_bottom .= "    <xi:include href=\"xml/$filename.xml\"/>\n";

                if (defined ($SourceSymbolDocs{"$TMPL_DIR/$filename:Include"})) {
                    if ($section_includes) {
                        &LogWarning ($file, $., "Section <INCLUDE> being overridden by inline comments.");
                    }
                    $section_includes = $SourceSymbolDocs{"$TMPL_DIR/$filename:Include"};
                }
                if ($section_includes eq "") {
                    $section_includes = $includes;
                }

                 $signals_synop =~ s/^\n*//g;
                 $signals_synop =~ s/\n+$/\n/g;
                if ($signals_synop ne '') {
                    $signals_synop = <<EOF;
<refsect1 id="$section_id.signals" role="signal_proto">
<title role="signal_proto.title">Signals</title>
<informaltable frame="none">
<tgroup cols="3">
<colspec colname="signals_return" colwidth="150px"/>
<colspec colname="signals_name" colwidth="300px"/>
<colspec colname="signals_flags" colwidth="200px"/>
<tbody>
${signals_synop}
</tbody>
</tgroup>
</informaltable>
</refsect1>
EOF
                     $signals_desc = TrimTextBlock($signals_desc);
                    $signals_desc  = <<EOF;
<refsect1 id="$section_id.signal-details" role="signals">
<title role="signals.title">Signal Details</title>
$signals_desc
</refsect1>
EOF
                }

                $args_synop =~ s/^\n*//g;
                $args_synop =~ s/\n+$/\n/g;
                if ($args_synop ne '') {
                    $args_synop = <<EOF;
<refsect1 id="$section_id.properties" role="properties">
<title role="properties.title">Properties</title>
<informaltable frame="none">
<tgroup cols="3">
<colspec colname="properties_type" colwidth="150px"/>
<colspec colname="properties_name" colwidth="300px"/>
<colspec colname="properties_flags" colwidth="200px"/>
<tbody>
${args_synop}
</tbody>
</tgroup>
</informaltable>
</refsect1>
EOF
                     $args_desc = TrimTextBlock($args_desc);
                    $args_desc  = <<EOF;
<refsect1 id="$section_id.property-details" role="property_details">
<title role="property_details.title">Property Details</title>
$args_desc
</refsect1>
EOF
                }

                $child_args_synop =~ s/^\n*//g;
                $child_args_synop =~ s/\n+$/\n/g;
                if ($child_args_synop ne '') {
                    $args_synop .= <<EOF;
<refsect1 id="$section_id.child-properties" role="child_properties">
<title role="child_properties.title">Child Properties</title>
<informaltable frame="none">
<tgroup cols="3">
<colspec colname="child_properties_type" colwidth="150px"/>
<colspec colname="child_properties_name" colwidth="300px"/>
<colspec colname="child_properties_flags" colwidth="200px"/>
<tbody>
${child_args_synop}
</tbody>
</tgroup>
</informaltable>
</refsect1>
EOF
                     $child_args_desc = TrimTextBlock($child_args_desc);
                     $args_desc .= <<EOF;
<refsect1 id="$section_id.child-property-details" role="child_property_details">
<title role="child_property_details.title">Child Property Details</title>
$child_args_desc
</refsect1>
EOF
                }

                $style_args_synop =~ s/^\n*//g;
                $style_args_synop =~ s/\n+$/\n/g;
                if ($style_args_synop ne '') {
                    $args_synop .= <<EOF;
<refsect1 id="$section_id.style-properties" role="style_properties">
<title role="style_properties.title">Style Properties</title>
<informaltable frame="none">
<tgroup cols="3">
<colspec colname="style_properties_type" colwidth="150px"/>
<colspec colname="style_properties_name" colwidth="300px"/>
<colspec colname="style_properties_flags" colwidth="200px"/>
<tbody>
${style_args_synop}
</tbody>
</tgroup>
</informaltable>
</refsect1>
EOF
                     $style_args_desc = TrimTextBlock($style_args_desc);
                    $args_desc .= <<EOF;
<refsect1 id="$section_id.style-property-details" role="style_properties_details">
<title role="style_properties_details.title">Style Property Details</title>
$style_args_desc
</refsect1>
EOF
                }

                $hierarchy_str = &AddTreeLineArt(\@hierarchy);
                if ($hierarchy_str ne "") {
                    $hierarchy_str = <<EOF;
<refsect1 id="$section_id.object-hierarchy" role="object_hierarchy">
<title role="object_hierarchy.title">Object Hierarchy</title>
<screen>$hierarchy_str
</screen>
</refsect1>
EOF
                }

                 $interfaces =~ TrimTextBlock($interfaces);
                if ($interfaces ne "") {
                    $interfaces = <<EOF;
<refsect1 id="$section_id.implemented-interfaces" role="impl_interfaces">
<title role="impl_interfaces.title">Implemented Interfaces</title>
$interfaces
</refsect1>
EOF
                }

                 $implementations = TrimTextBlock($implementations);
                if ($implementations ne "") {
                    $implementations = <<EOF;
<refsect1 id="$section_id.implementations" role="implementations">
<title role="implementations.title">Known Implementations</title>
$implementations
</refsect1>
EOF
                }

                 $prerequisites = TrimTextBlock($prerequisites);
                if ($prerequisites ne "") {
                    $prerequisites = <<EOF;
<refsect1 id="$section_id.prerequisites" role="prerequisites">
<title role="prerequisites.title">Prerequisites</title>
$prerequisites
</refsect1>
EOF
                }

                 $derived = TrimTextBlock($derived);
                if ($derived ne "") {
                    $derived = <<EOF;
<refsect1 id="$section_id.derived-interfaces" role="derived_interfaces">
<title role="derived_interfaces.title">Known Derived Interfaces</title>
$derived
</refsect1>
EOF
                }

                $functions_synop =~ s/^\n*//g;
                $functions_synop =~ s/\n+$/\n/g;
                if ($functions_synop ne '') {
                  $functions_synop = <<EOF;
<refsect1 id="$section_id.functions" role="functions_proto">
<title role="functions_proto.title">Functions</title>
<informaltable pgwide="1" frame="none">
<tgroup cols="2">
<colspec colname="functions_return" colwidth="150px"/>
<colspec colname="functions_name"/>
<tbody>
${functions_synop}
</tbody>
</tgroup>
</informaltable>
</refsect1>
EOF
                }

                $other_synop =~ s/^\n*//g;
                $other_synop =~ s/\n+$/\n/g;
                if ($other_synop ne '') {
                  $other_synop = <<EOF;
<refsect1 id="$section_id.other" role="other_proto">
<title role="other_proto.title">Types and Values</title>
<informaltable role="enum_members_table" pgwide="1" frame="none">
<tgroup cols="2">
<colspec colname="name" colwidth="150px"/>
<colspec colname="description"/>
<tbody>
${other_synop}
</tbody>
</tgroup>
</informaltable>
</refsect1>
EOF
                }

                my $file_changed = &OutputDBFile ($filename, $title, $section_id,
                                                    $section_includes,
                                                    \$functions_synop, \$other_synop,
                                                    \$functions_details, \$other_details,
                                                    \$signals_synop, \$signals_desc,
                                                    \$args_synop, \$args_desc,
                                                    \$hierarchy_str, \$interfaces,
                                                    \$implementations,
                                                    \$prerequisites, \$derived,
                                                    \@file_objects);
                if ($file_changed) {
                    $changed = 1;
                }
            }
            $title = "";
            $section_id = "";
            $subsection = "";
            $in_section = 0;
            $section_includes = "";
            $functions_synop = "";
            $other_synop = "";
            $functions_details = "";
            $other_details = "";
            $signals_synop = "";
            $signals_desc = "";
            $args_synop = "";
            $child_args_synop = "";
            $style_args_synop = "";
            $args_desc = "";
            $child_args_desc = "";
            $style_args_desc = "";
            $hierarchy_str = "";
            @hierarchy = ();
            $interfaces = "";
            $implementations = "";
            $prerequisites = "";
            $derived = "";

        } elsif (m/^(\S+)/) {
            my $symbol = $1;
            #("  Symbol: $symbol in subsection: $subsection\n");

            # check for duplicate entries
            if (! defined $symbol_def_line{$symbol}) {
                my $declaration = $Declarations{$symbol};
                if (defined ($declaration)) {
                    if (&CheckIsObject ($symbol)) {
                        push @file_objects, $symbol;
                    }
                    # We don't want standard macros/functions of GObjects,
                    # or private declarations.
                    if ($subsection ne "Standard" && $subsection ne "Private") {
                        my ($synop, $desc) = &OutputDeclaration ($symbol,
                                                                 $declaration);
                        my $type = $DeclarationTypes {$symbol};
        
                        if ($type eq 'FUNCTION' || $type eq 'USER_FUNCTION') {
                          $functions_synop .= $synop;
                          $functions_details .= $desc;
                        } elsif ($type eq 'MACRO' && $declaration =~ /$symbol\(/) {
                          $functions_synop .= $synop;
                          $functions_details .= $desc;
                        } else {
                          $other_synop .= $synop;
                          $other_details .= $desc;
                        }
                    }
                    my ($sig_synop, $sig_desc) = &GetSignals ($symbol);
                    my ($arg_synop, $child_arg_synop, $style_arg_synop,
                        $arg_desc, $child_arg_desc, $style_arg_desc) = &GetArgs ($symbol);
                    my $ifaces = &GetInterfaces ($symbol);
                    my $impls = &GetImplementations ($symbol);
                    my $prereqs = &GetPrerequisites ($symbol);
                    my $der = &GetDerived ($symbol);
                    @hierarchy = &GetHierarchy ($symbol, \@hierarchy);

                    $signals_synop .= $sig_synop;
                    $signals_desc .= $sig_desc;
                    $args_synop .= $arg_synop;
                    $child_args_synop .= $child_arg_synop;
                    $style_args_synop .= $style_arg_synop;
                    $args_desc .= $arg_desc;
                    $child_args_desc .= $child_arg_desc;
                    $style_args_desc .= $style_arg_desc;
                    $interfaces .= $ifaces;
                    $implementations .= $impls;
                    $prerequisites .= $prereqs;
                    $derived .= $der;

                    # Note that the declaration has been output.
                    $DeclarationOutput{$symbol} = 1;
                } elsif ($subsection ne "Standard" && $subsection ne "Private") {
                    $UndeclaredSymbols{$symbol} = 1;
                    &LogWarning ($file, $., "No declaration found for $symbol.");
                }
                $num_symbols++;
                $symbol_def_line{$symbol}=$.;

                if ($section_id eq "") {
                    if($title eq "" && $filename eq "") {
                        &LogWarning ($file, $., "Section has no title and no file.");
                    }
                    # FIXME: one of those would be enough
                    # filename should be an internal detail for gtk-doc
                    if ($title eq "") {
                        $title = $filename;
                    } elsif ($filename eq "") {
                        $filename = $title;
                    }
                    $filename =~ s/\s/_/g;

                    $section_id = $SourceSymbolDocs{"$TMPL_DIR/$filename:Section_Id"};
                    if (defined ($section_id) && $section_id !~ m/^\s*$/) {
                        # Remove trailing blanks and use as is
                        $section_id =~ s/\s+$//;
                    } elsif (&CheckIsObject ($title)) {
                        # GObjects use their class name as the ID.
                        $section_id = &CreateValidSGMLID ($title);
                    } else {
                        $section_id = &CreateValidSGMLID ("$MODULE-$title");
                    }
                }
                $SymbolSection{$symbol}=$title;
                $SymbolSectionId{$symbol}=$section_id;
            }
            else {
                &LogWarning ($file, $., "Double symbol entry for $symbol. ".
                    "Previous occurrence on line ".$symbol_def_line{$symbol}.".");
            }
        }
    }
    close (INPUT);

    &OutputMissingDocumentation;
    &OutputUndeclaredSymbols;
    &OutputUnusedSymbols;

    if ($OUTPUT_ALL_SYMBOLS) {
        &OutputAllSymbols;
    }
    if ($OUTPUT_SYMBOLS_WITHOUT_SINCE) {
        &OutputSymbolsWithoutSince;
    }

    for $filename (split (' ', $EXPAND_CONTENT_FILES)) {
        my $file_changed = &OutputExtraFile ($filename);
        if ($file_changed) {
            $changed = 1;
        }
    }

    &OutputBook ($book_top, $book_bottom);

    return $changed;
}

#############################################################################
# Function    : OutputIndex
# Description : This writes an indexlist that can be included into the main-
#               document into an <index> tag.
#############################################################################

sub OutputIndex {
    my ($basename, $apiindexref ) = @_;
    my %apiindex = %{$apiindexref};
    my $old_index = "$DB_OUTPUT_DIR/$basename.xml";
    my $new_index = "$DB_OUTPUT_DIR/$basename.new";
    my $lastletter = " ";
    my $divopen = 0;
    my $symbol;
    my $short_symbol;

    open (OUTPUT, ">$new_index")
        || die "Can't create $new_index";

    print (OUTPUT &MakeDocHeader ("indexdiv")."\n<indexdiv id=\"$basename\">\n");

    #("generate $basename index (".%apiindex." entries)\n");

    # do a case insensitive sort while chopping off the prefix
    foreach my $hash (
        sort { $$a{criteria} cmp $$b{criteria} or $$a{original} cmp $$b{original} }
        map { my $x = uc($_); $x =~ s/^$NAME_SPACE\_?(.*)/$1/i; { criteria => $x, original => $_, short => $1 } }
        keys %apiindex) {

        $symbol = $$hash{original};
        if (defined($$hash{short})) {
            $short_symbol = $$hash{short};
        } else {
            $short_symbol = $symbol;
        }

        # generate a short symbol description
        my $symbol_desc = "";
        my $symbol_section = "";
        my $symbol_section_id = "";
        my $symbol_type = "";
        if (defined($DeclarationTypes{$symbol})) {
          $symbol_type = lc($DeclarationTypes{$symbol});
        }
        if ($symbol_type eq "") {
            #("trying symbol $symbol\n");
            if ($symbol =~ m/(.*)::(.*)/) {
                my $oname = $1;
                my $osym = $2;
                my $i;
                #("  trying object signal ${oname}:$osym in ".$#SignalNames." signals\n");
                for ($i = 0; $i <= $#SignalNames; $i++) {
                    if ($SignalNames[$i] eq $osym) {
                        $symbol_type = "object signal";
                        if (defined($SymbolSection{$oname})) {
                           $symbol_section = $SymbolSection{$oname};
                           $symbol_section_id = $SymbolSectionId{$oname};
                        }
                        last;
                    }
                }
            } elsif ($symbol =~ m/(.*):(.*)/) {
                my $oname = $1;
                my $osym = $2;
                my $i;
                #("  trying object property ${oname}::$osym in ".$#ArgNames." properties\n");
                for ($i = 0; $i <= $#ArgNames; $i++) {
                    #("    ".$ArgNames[$i]."\n");
                    if ($ArgNames[$i] eq $osym) {
                        $symbol_type = "object property";
                        if (defined($SymbolSection{$oname})) {
                           $symbol_section = $SymbolSection{$oname};
                           $symbol_section_id = $SymbolSectionId{$oname};
                        }
                        last;
                    }
                }
            }
        } else {
           if (defined($SymbolSection{$symbol})) {
               $symbol_section = $SymbolSection{$symbol};
               $symbol_section_id = $SymbolSectionId{$symbol};
           }
        }
        if ($symbol_type ne "") {
           $symbol_desc=", $symbol_type";
           if ($symbol_section ne "") {
               $symbol_desc.=" in <link linkend=\"$symbol_section_id\">$symbol_section</link>";
               #$symbol_desc.=" in ". &ExpandAbbreviations($symbol, "#$symbol_section");
           }
        }

        my $curletter = uc(substr($short_symbol,0,1));
        my $id = $apiindex{$symbol};

        #("  add symbol $symbol with $id to index in section $curletter\n");

        if ($curletter ne $lastletter) {
            $lastletter = $curletter;

            if ($divopen == 1) {
                print (OUTPUT "</indexdiv>\n");
            }
            print (OUTPUT "<indexdiv><title>$curletter</title>\n");
            $divopen = 1;
        }

        print (OUTPUT <<EOF);
<indexentry><primaryie linkends="$id"><link linkend="$id">$symbol</link>$symbol_desc</primaryie></indexentry>
EOF
    }

    if ($divopen == 1) {
        print (OUTPUT "</indexdiv>\n");
    }
    print (OUTPUT "</indexdiv>\n");
    close (OUTPUT);

    &UpdateFileIfChanged ($old_index, $new_index, 0);
}


#############################################################################
# Function    : OutputIndexFull
# Description : This writes the full api indexlist that can be included into the
#               main document into an <index> tag.
#############################################################################

sub OutputIndexFull {
    &OutputIndex ("api-index-full", \%IndexEntriesFull);
}


#############################################################################
# Function    : OutputDeprecatedIndex
# Description : This writes the deprecated api indexlist that can be included
#               into the main document into an <index> tag.
#############################################################################

sub OutputDeprecatedIndex {
    &OutputIndex ("api-index-deprecated", \%IndexEntriesDeprecated);
}


#############################################################################
# Function    : OutputSinceIndexes
# Description : This writes the 'since' api indexlists that can be included into
#               the main document into an <index> tag.
#############################################################################

sub OutputSinceIndexes {
    my @sinces = keys %{{ map { $_ => 1 } values %Since }};

    foreach my $version (@sinces) {
        #("Since : [$version]\n");
        # TODO make filtered hash
        #my %index = grep { $Since{$_} eq $version } %IndexEntriesSince;
        my %index = map { $_ => $IndexEntriesSince{$_} } grep { $Since{$_} eq $version } keys %IndexEntriesSince;

        &OutputIndex ("api-index-$version", \%index);
    }
}

#############################################################################
# Function    : OutputAnnotationGlossary
# Description : This writes a glossary of the used annotation terms into a
#               separate glossary file that can be included into the main
#               document.
#############################################################################

sub OutputAnnotationGlossary {
    my $old_glossary = "$DB_OUTPUT_DIR/annotation-glossary.xml";
    my $new_glossary = "$DB_OUTPUT_DIR/annotation-glossary.new";
    my $lastletter = " ";
    my $divopen = 0;

    # if there are no annotations used return
    return if (! keys(%AnnotationsUsed));

    # add acronyms that are referenced from acronym text
rerun:
    foreach my $annotation (keys(%AnnotationsUsed)) {
        if(defined($AnnotationDefinition{$annotation})) {
            if($AnnotationDefinition{$annotation} =~ m/<acronym>([\w ]+)<\/acronym>/) {
                if (!exists($AnnotationsUsed{$1})) {
                    $AnnotationsUsed{$1} = 1;
                    goto rerun;
                }
            }
        }
    }

    open (OUTPUT, ">$new_glossary")
        || die "Can't create $new_glossary";

    print (OUTPUT  <<EOF);
${\( MakeDocHeader ("glossary") )}
<glossary id="annotation-glossary">
  <title>Annotation Glossary</title>
EOF

    foreach my $annotation (sort({lc $a cmp lc $b} keys(%AnnotationsUsed))) {
        if(defined($AnnotationDefinition{$annotation})) {
            my $def = $AnnotationDefinition{$annotation};
            my $curletter = uc(substr($annotation,0,1));

            if ($curletter ne $lastletter) {
                $lastletter = $curletter;

                if ($divopen == 1) {
                    print (OUTPUT "</glossdiv>\n");
                }
                print (OUTPUT "<glossdiv><title>$curletter</title>\n");
                $divopen = 1;
            }
            print (OUTPUT <<EOF);
    <glossentry>
      <glossterm><anchor id="annotation-glossterm-$annotation"/>$annotation</glossterm>
      <glossdef>
        <para>$def</para>
      </glossdef>
    </glossentry>
EOF
        }
    }

    if ($divopen == 1) {
        print (OUTPUT "</glossdiv>\n");
    }
    print (OUTPUT "</glossary>\n");
    close (OUTPUT);

    &UpdateFileIfChanged ($old_glossary, $new_glossary, 0);
}

#############################################################################
# Function    : ReadKnownSymbols
# Description : This collects the names of non-private symbols from the
#               $MODULE-sections.txt file.
# Arguments   : $file - the $MODULE-sections.txt file which contains all of
#                the functions/macros/structs etc. being documented, organised
#                into sections and subsections.
#############################################################################

sub ReadKnownSymbols {
    my ($file) = @_;

    my $subsection = "";

    #("Reading: $file\n");
    open (INPUT, $file)
        || die "Can't open $file: $!";

    while (<INPUT>) {
        if (m/^#/) {
            next;

        } elsif (m/^<SECTION>/) {
            $subsection = "";

        } elsif (m/^<SUBSECTION\s*(.*)>/i) {
            $subsection = $1;

        } elsif (m/^<SUBSECTION>/) {
            next;

        } elsif (m/^<TITLE>(.*)<\/TITLE>/) {
            next;

        } elsif (m/^<FILE>(.*)<\/FILE>/) {
            $KnownSymbols{"$TMPL_DIR/$1:Long_Description"} = 1;
            $KnownSymbols{"$TMPL_DIR/$1:Short_Description"} = 1;
            next;

        } elsif (m/^<INCLUDE>(.*)<\/INCLUDE>/) {
            next;

        } elsif (m/^<\/SECTION>/) {
            next;

        } elsif (m/^(\S+)/) {
            my $symbol = $1;

            if ($subsection ne "Standard" && $subsection ne "Private") {
                $KnownSymbols{$symbol} = 1;
            }
            else {
                $KnownSymbols{$symbol} = 0;
            }
        }
    }
    close (INPUT);
}


#############################################################################
# Function    : OutputDeclaration
# Description : Returns the synopsis and detailed description DocBook
#                describing one function/macro etc.
# Arguments   : $symbol - the name of the function/macro begin described.
#                $declaration - the declaration of the function/macro.
#############################################################################

sub OutputDeclaration {
    my ($symbol, $declaration) = @_;

    my $type = $DeclarationTypes {$symbol};
    if ($type eq 'MACRO') {
        return &OutputMacro ($symbol, $declaration);
    } elsif ($type eq 'TYPEDEF') {
        return &OutputTypedef ($symbol, $declaration);
    } elsif ($type eq 'STRUCT') {
        return &OutputStruct ($symbol, $declaration);
    } elsif ($type eq 'ENUM') {
        return &OutputEnum ($symbol, $declaration);
    } elsif ($type eq 'UNION') {
        return &OutputUnion ($symbol, $declaration);
    } elsif ($type eq 'VARIABLE') {
        return &OutputVariable ($symbol, $declaration);
    } elsif ($type eq 'FUNCTION') {
        return &OutputFunction ($symbol, $declaration, $type);
    } elsif ($type eq 'USER_FUNCTION') {
        return &OutputFunction ($symbol, $declaration, $type);
    } else {
        die "Unknown symbol type";
    }
}


#############################################################################
# Function    : OutputSymbolTraits
# Description : Returns the Since and StabilityLevel paragraphs for a symbol.
# Arguments   : $symbol - the name of the function/macro begin described.
#############################################################################

sub OutputSymbolTraits {
    my ($symbol) = @_;
    my $desc = "";

    if (exists $Since{$symbol}) {
        my $link_id = "api-index-".$Since{$symbol};
        $desc .= "<para role=\"since\">Since: <link linkend=\"$link_id\">$Since{$symbol}</link></para>";
    }
    if (exists $StabilityLevel{$symbol}) {
        my $stability = $StabilityLevel{$symbol};
        $AnnotationsUsed{$stability} = 1;
        $desc .= "<para role=\"stability\">Stability Level: <acronym>$stability</acronym></para>";
    }
    return $desc;
}

#############################################################################
# Function    : Output{Symbol,Section}ExtraLinks
# Description : Returns extralinks for the symbol (if enabled).
# Arguments   : $symbol - the name of the function/macro begin described.
#############################################################################

sub uri_escape {
    my $text = $_[0];
    return undef unless defined $text;

    # Build a char to hex map
    my %escapes = ();
    for (0..255) {
            $escapes{chr($_)} = sprintf("%%%02X", $_);
    }

    # Default unsafe characters.  RFC 2732 ^(uric - reserved)
    $text =~ s/([^A-Za-z0-9\-_.!~*'()])/$escapes{$1}/g;

    return $text;
}

sub OutputSymbolExtraLinks {
    my ($symbol) = @_;
    my $desc = "";

    if (0) { # NEW FEATURE: needs configurability
    my $sstr = &uri_escape($symbol);
    my $mstr = &uri_escape($MODULE);
    $desc .= <<EOF;
<ulink role="extralinks" url="http://www.google.com/codesearch?q=$sstr">code search</ulink>
<ulink role="extralinks" url="http://library.gnome.org/edit?module=$mstr&amp;symbol=$sstr">edit documentation</ulink>
EOF
    }
    return $desc;
}

sub OutputSectionExtraLinks {
    my ($symbol,$docsymbol) = @_;
    my $desc = "";

    if (0) { # NEW FEATURE: needs configurability
    my $sstr = &uri_escape($symbol);
    my $mstr = &uri_escape($MODULE);
    my $dsstr = &uri_escape($docsymbol);
    $desc .= <<EOF;
<ulink role="extralinks" url="http://www.google.com/codesearch?q=$sstr">code search</ulink>
<ulink role="extralinks" url="http://library.gnome.org/edit?module=$mstr&amp;symbol=$dsstr">edit documentation</ulink>
EOF
    }
    return $desc;
}


#############################################################################
# Function    : OutputMacro
# Description : Returns the synopsis and detailed description of a macro.
# Arguments   : $symbol - the macro.
#                $declaration - the declaration of the macro.
#############################################################################

sub OutputMacro {
    my ($symbol, $declaration) = @_;
    my $id = &CreateValidSGMLID ($symbol);
    my $condition = &MakeConditionDescription ($symbol);
    my $synop = "<row><entry role=\"define_keyword\">#define</entry><entry role=\"function_name\"><link linkend=\"$id\">$symbol</link>";
    my $desc;

    my @fields = ParseMacroDeclaration($declaration, \&CreateValidSGML);
    my $title = $symbol . (@fields ? "()" : "");

    $desc = "<refsect2 id=\"$id\" role=\"macro\"$condition>\n<title>$title</title>\n";
    $desc .= MakeIndexterms($symbol, $id);
    $desc .= "\n";
    $desc .= OutputSymbolExtraLinks($symbol);

    if (@fields) {
        $synop .= "<phrase role=\"c_punctuation\">()</phrase>";
    }
    $synop .= "</entry></row>\n";

    # Don't output the macro definition if is is a conditional macro or it
    # looks like a function, i.e. starts with "g_" or "_?gnome_", or it is
    # longer than 2 lines, otherwise we get lots of complicated macros like
    # g_assert.
    if (!defined ($DeclarationConditional{$symbol}) && ($symbol !~ m/^g_/)
        && ($symbol !~ m/^_?gnome_/) && (($declaration =~ tr/\n//) < 2)) {
        my $decl_out = &CreateValidSGML ($declaration);
        $desc .= "<programlisting language=\"C\">$decl_out</programlisting>\n";
    } else {
        $desc .= "<programlisting language=\"C\">" . &MakeReturnField("#define") . "$symbol";
        if ($declaration =~ m/^\s*#\s*define\s+\w+(\([^\)]*\))/) {
            my $args = $1;
            my $pad = ' ' x ($RETURN_TYPE_FIELD_WIDTH - length ("#define "));
            # Align each line so that if should all line up OK.
            $args =~ s/\n/\n$pad/gm;
            $desc .= &CreateValidSGML ($args);
        }
        $desc .= "</programlisting>\n";
    }

    $desc .= &MakeDeprecationNote($symbol);

    my $parameters = &OutputParamDescriptions ("MACRO", $symbol, @fields);

    if (defined ($SymbolDocs{$symbol})) {
        my $symbol_docs = &ConvertMarkDown($symbol, $SymbolDocs{$symbol});
        $desc .= $symbol_docs;
    }

    $desc .= $parameters;
    $desc .= OutputSymbolTraits ($symbol);
    $desc .= "</refsect2>\n";
    return ($synop, $desc);
}


#############################################################################
# Function    : OutputTypedef
# Description : Returns the synopsis and detailed description of a typedef.
# Arguments   : $symbol - the typedef.
#                $declaration - the declaration of the typedef,
#                  e.g. 'typedef unsigned int guint;'
#############################################################################

sub OutputTypedef {
    my ($symbol, $declaration) = @_;
    my $id = &CreateValidSGMLID ($symbol);
    my $condition = &MakeConditionDescription ($symbol);
    my $desc = "<refsect2 id=\"$id\" role=\"typedef\"$condition>\n<title>$symbol</title>\n";
    my $synop = "<row><entry role=\"typedef_keyword\">typedef</entry><entry role=\"function_name\"><link linkend=\"$id\">$symbol</link></entry></row>\n";

    $desc .= MakeIndexterms($symbol, $id);
    $desc .= "\n";
    $desc .= OutputSymbolExtraLinks($symbol);

    if (!defined ($DeclarationConditional{$symbol})) {
        my $decl_out = &CreateValidSGML ($declaration);
        $desc .= "<programlisting language=\"C\">$decl_out</programlisting>\n";
    }

    $desc .= &MakeDeprecationNote($symbol);

    if (defined ($SymbolDocs{$symbol})) {
        $desc .= &ConvertMarkDown($symbol, $SymbolDocs{$symbol});
    }
    $desc .= OutputSymbolTraits ($symbol);
    $desc .= "</refsect2>\n";
    return ($synop, $desc);
}


#############################################################################
# Function    : OutputStruct
# Description : Returns the synopsis and detailed description of a struct.
#                We check if it is a object struct, and if so we only output
#                parts of it that are noted as public fields.
#                We also use a different IDs for object structs, since the
#                original ID is used for the entire RefEntry.
# Arguments   : $symbol - the struct.
#                $declaration - the declaration of the struct.
#############################################################################

sub OutputStruct {
    my ($symbol, $declaration) = @_;

    my $is_gtype = 0;
    my $default_to_public = 1;
    if (&CheckIsObject ($symbol)) {
        #("Found struct gtype: $symbol\n");
        $is_gtype = 1;
        $default_to_public = $ObjectRoots{$symbol} eq 'GBoxed';
    }

    my $id;
    my $condition;
    if ($is_gtype) {
        $id = &CreateValidSGMLID ($symbol . "_struct");
        $condition = &MakeConditionDescription ($symbol . "_struct");
    } else {
        $id = &CreateValidSGMLID ($symbol);
        $condition = &MakeConditionDescription ($symbol);
    }

    # Determine if it is a simple struct or it also has a typedef.
    my $has_typedef = 0;
    if ($StructHasTypedef{$symbol} || $declaration =~ m/^\s*typedef\s+/) {
      $has_typedef = 1;
    }

    my $type_output;
    my $desc;
    if ($has_typedef) {
        # For structs with typedefs we just output the struct name.
        $type_output = "";
        $desc = "<refsect2 id=\"$id\" role=\"struct\"$condition>\n<title>$symbol</title>\n";
    } else {
        $type_output = "struct";
        $desc = "<refsect2 id=\"$id\" role=\"struct\"$condition>\n<title>struct $symbol</title>\n";
    }
    my $synop = "<row><entry role=\"datatype_keyword\">${type_output}</entry><entry role=\"function_name\"><link linkend=\"$id\">$symbol</link></entry></row>\n";

    $desc .= MakeIndexterms($symbol, $id);
    $desc .= "\n";
    $desc .= OutputSymbolExtraLinks($symbol);

    # Form a pretty-printed, private-data-removed form of the declaration

    my $decl_out = "";
    if ($declaration =~ m/^\s*$/) {
        #("Found opaque struct: $symbol\n");
        $decl_out = "typedef struct _$symbol $symbol;";
    } elsif ($declaration =~ m/^\s*struct\s+\w+\s*;\s*$/) {
        #("Found opaque struct: $symbol\n");
        $decl_out = "struct $symbol;";
    } else {
        my $public = $default_to_public;
        my $new_declaration = "";
        my $decl_line;
        my $decl = $declaration;

        if ($decl =~ m/^\s*(typedef\s+)?struct\s*\w*\s*(?:\/\*.*\*\/)?\s*{(.*)}\s*\w*\s*;\s*$/s) {
            my $struct_contents = $2;

            foreach $decl_line (split (/\n/, $struct_contents)) {
                #("Struct line: $decl_line\n");
                if ($decl_line =~ m%/\*\s*<\s*public\s*>\s*\*/%) {
                    $public = 1;
                } elsif ($decl_line =~ m%/\*\s*<\s*(private|protected)\s*>\s*\*/%) {
                    $public = 0;
                } elsif ($public) {
                    $new_declaration .= $decl_line . "\n";
                }
            }

            if ($new_declaration) {
                # Strip any blank lines off the ends.
                $new_declaration =~ s/^\s*\n//;
                $new_declaration =~ s/\n\s*$/\n/;

                if ($has_typedef) {
                    $decl_out = "typedef struct {\n" . $new_declaration
                      . "} $symbol;\n";
                } else {
                    $decl_out = "struct $symbol {\n" . $new_declaration
                      . "};\n";
                }
            }
        } else {
            &LogWarning (&GetSymbolSourceFile ($symbol), &GetSymbolSourceLine($symbol),
                "Couldn't parse struct:\n$declaration");
        }

        # If we couldn't parse the struct or it was all private, output an
        # empty struct declaration.
        if ($decl_out eq "") {
            if ($has_typedef) {
                $decl_out = "typedef struct _$symbol $symbol;";
            } else {
                $decl_out = "struct $symbol;";
            }
        }
    }

    $decl_out = &CreateValidSGML ($decl_out);
    $desc .= "<programlisting language=\"C\">$decl_out</programlisting>\n";

    $desc .= &MakeDeprecationNote($symbol);

    if (defined ($SymbolDocs{$symbol})) {
        $desc .= &ConvertMarkDown($symbol, $SymbolDocs{$symbol});
    }

    # Create a table of fields and descriptions

    # FIXME: Inserting &#160's into the produced type declarations here would
    #        improve the output in most situations ... except for function
    #        members of structs!
    my @fields = ParseStructDeclaration($declaration, !$default_to_public,
                                        0, \&MakeXRef,
                                        sub {
                                            "<structfield id=\"".&CreateValidSGMLID("$id.$_[0]")."\">$_[0]</structfield>";
                                        });
    my $params = $SymbolParams{$symbol};

    # If no parameters are filled in, we don't generate the description
    # table, for backwards compatibility.

    my $found = 0;
    if (defined $params) {
        for (my $i = 1; $i <= $#$params; $i += $PARAM_FIELD_COUNT) {
            if ($params->[$i] =~ /\S/) {
                $found = 1;
                last;
            }
        }
    }

    if ($found) {
        my %field_descrs = @$params;
        my $missing_parameters = "";
        my $unused_parameters = "";
        my $id = &CreateValidSGMLID ("$symbol".".members");

        $desc .= <<EOF;
<refsect3 id="$id" role="struct_members">\n<title>Members</title>
<informaltable role="struct_members_table" pgwide="1" frame="none">
<tgroup cols="3">
<colspec colname="struct_members_name" colwidth="300px"/>
<colspec colname="struct_members_description"/>
<colspec colname="struct_members_annotations" colwidth="200px"/>
<tbody>
EOF

        while (@fields) {
            my $field_name = shift @fields;
            my $text = shift @fields;
            my $field_descr = $field_descrs{$field_name};
            my $param_annotations = "";

            $desc .= "<row role=\"member\"><entry role=\"struct_member_name\"><para>$text</para></entry>\n";
            if (defined $field_descr) {
                ($field_descr,$param_annotations) = &ExpandAnnotation($symbol, $field_descr);
                $field_descr = &ConvertMarkDown($symbol, $field_descr);
                # trim
                $field_descr =~ s/^(\s|\n)+//msg;
                $field_descr =~ s/(\s|\n)+$//msg;
                $desc .= "<entry role=\"struct_member_description\">$field_descr</entry>\n<entry role=\"struct_member_annotations\">$param_annotations</entry>\n";
                delete $field_descrs{$field_name};
            } else {
                &LogWarning (&GetSymbolSourceFile ($symbol), &GetSymbolSourceLine($symbol),
                    "Field description for $symbol"."::"."$field_name is missing in source code comment block.");
                if ($missing_parameters ne "") {
                  $missing_parameters .= ", ".$field_name;
                } else {
                    $missing_parameters = $field_name;
                }
                $desc .= "<entry /><entry />\n";
            }
            $desc .= "</row>\n";
        }
        $desc .= "</tbody></tgroup></informaltable>\n</refsect3>\n";
        foreach my $field_name (keys %field_descrs) {
            # Documenting those standard fields is not required anymore, but
            # we don't want to warn if they are documented anyway.
            if ($field_name =~ /(g_iface|parent_instance|parent_class)/) {
                next;
            }
            &LogWarning (&GetSymbolSourceFile ($symbol), &GetSymbolSourceLine($symbol),
                "Field description for $symbol"."::"."$field_name is not used from source code comment block.");
            if ($unused_parameters ne "") {
              $unused_parameters .= ", ".$field_name;
            } else {
               $unused_parameters = $field_name;
            }
        }

        # remember missing/unused parameters (needed in tmpl-free build)
        if (($missing_parameters ne "") and (! exists ($AllIncompleteSymbols{$symbol}))) {
            $AllIncompleteSymbols{$symbol}=$missing_parameters;
        }
        if (($unused_parameters ne "") and (! exists ($AllUnusedSymbols{$symbol}))) {
            $AllUnusedSymbols{$symbol}=$unused_parameters;
        }
    }
    else {
        if (scalar(@fields) > 0) {
            if (! exists ($AllIncompleteSymbols{$symbol})) {
                $AllIncompleteSymbols{$symbol}="<items>";
                &LogWarning (&GetSymbolSourceFile ($symbol), &GetSymbolSourceLine($symbol),
                    "Field descriptions for struct $symbol are missing in source code comment block.");
                #("Remaining structs fields: ".@fields.":".join(',',@fields)."\n");
            }
        }
    }

    $desc .= OutputSymbolTraits ($symbol);
    $desc .= "</refsect2>\n";
    return ($synop, $desc);
}


#############################################################################
# Function    : OutputUnion
# Description : Returns the synopsis and detailed description of a union.
# Arguments   : $symbol - the union.
#                $declaration - the declaration of the union.
#############################################################################

sub OutputUnion {
    my ($symbol, $declaration) = @_;

    my $is_gtype = 0;
    if (&CheckIsObject ($symbol)) {
        #("Found union gtype: $symbol\n");
        $is_gtype = 1;
    }

    my $id;
    my $condition;
    if ($is_gtype) {
        $id = &CreateValidSGMLID ($symbol . "_union");
        $condition = &MakeConditionDescription ($symbol . "_union");
    } else {
        $id = &CreateValidSGMLID ($symbol);
        $condition = &MakeConditionDescription ($symbol);
    }

    # Determine if it is a simple struct or it also has a typedef.
    my $has_typedef = 0;
    if ($StructHasTypedef{$symbol} || $declaration =~ m/^\s*typedef\s+/) {
      $has_typedef = 1;
    }

    my $type_output;
    my $desc;
    if ($has_typedef) {
        # For unions with typedefs we just output the union name.
        $type_output = "";
        $desc = "<refsect2 id=\"$id\" role=\"union\"$condition>\n<title>$symbol</title>\n";
    } else {
        $type_output = "union";
        $desc = "<refsect2 id=\"$id\" role=\"union\"$condition>\n<title>union $symbol</title>\n";
    }
    my $synop = "<row><entry role=\"datatype_keyword\">${type_output}</entry><entry role=\"function_name\"><link linkend=\"$id\">$symbol</link></entry></row>\n";

    $desc .= MakeIndexterms($symbol, $id);
    $desc .= "\n";
    $desc .= OutputSymbolExtraLinks($symbol);
    $desc .= &MakeDeprecationNote($symbol);

    if (defined ($SymbolDocs{$symbol})) {
        $desc .= &ConvertMarkDown($symbol, $SymbolDocs{$symbol});
    }

    # Create a table of fields and descriptions

    # FIXME: Inserting &#160's into the produced type declarations here would
    #        improve the output in most situations ... except for function
    #        members of structs!
    my @fields = ParseStructDeclaration($declaration, 0,
                                        0, \&MakeXRef,
                                        sub {
                                            "<structfield id=\"".&CreateValidSGMLID("$id.$_[0]")."\">$_[0]</structfield>";
                                        });
    my $params = $SymbolParams{$symbol};

    # If no parameters are filled in, we don't generate the description
    # table, for backwards compatibility

    my $found = 0;
    if (defined $params) {
        for (my $i = 1; $i <= $#$params; $i += $PARAM_FIELD_COUNT) {
            if ($params->[$i] =~ /\S/) {
                $found = 1;
                last;
            }
        }
    }

    if ($found) {
        my %field_descrs = @$params;
        my $missing_parameters = "";
        my $unused_parameters = "";
        my $id = &CreateValidSGMLID ("$symbol".".members");

        $desc .= <<EOF;
<refsect3 id="$id" role="union_members">\n<title>Members</title>
<informaltable role="union_members_table" pgwide="1" frame="none">
<tgroup cols="3">
<colspec colname="union_members_name" colwidth="300px"/>
<colspec colname="union_members_description"/>
<colspec colname="union_members_annotations" colwidth="200px"/>
<tbody>
EOF

        while (@fields) {
            my $field_name = shift @fields;
            my $text = shift @fields;
            my $field_descr = $field_descrs{$field_name};
            my $param_annotations = "";

            $desc .= "<row><entry role=\"union_member_name\"><para>$text</para></entry>\n";
            if (defined $field_descr) {
                ($field_descr,$param_annotations) = &ExpandAnnotation($symbol, $field_descr);
                $field_descr = &ConvertMarkDown($symbol, $field_descr);

                # trim
                $field_descr =~ s/^(\s|\n)+//msg;
                $field_descr =~ s/(\s|\n)+$//msg;
                $desc .= "<entry role=\"union_member_description\">$field_descr</entry>\n<entry role=\"union_member_annotations\">$param_annotations</entry>\n";
                delete $field_descrs{$field_name};
            } else {
                &LogWarning (&GetSymbolSourceFile ($symbol), &GetSymbolSourceLine($symbol),
                    "Field description for $symbol"."::"."$field_name is missing in source code comment block.");
                if ($missing_parameters ne "") {
                    $missing_parameters .= ", ".$field_name;
                } else {
                    $missing_parameters = $field_name;
                }
                $desc .= "<entry /><entry />\n";
            }
            $desc .= "</row>\n";
        }
        $desc .= "</tbody></tgroup></informaltable>\n</refsect3>";
        foreach my $field_name (keys %field_descrs) {
            &LogWarning (&GetSymbolSourceFile ($symbol), &GetSymbolSourceLine($symbol),
                "Field description for $symbol"."::"."$field_name is not used from source code comment block.");
            if ($unused_parameters ne "") {
              $unused_parameters .= ", ".$field_name;
            } else {
               $unused_parameters = $field_name;
            }
        }

        # remember missing/unused parameters (needed in tmpl-free build)
        if (($missing_parameters ne "") and (! exists ($AllIncompleteSymbols{$symbol}))) {
            $AllIncompleteSymbols{$symbol}=$missing_parameters;
        }
        if (($unused_parameters ne "") and (! exists ($AllUnusedSymbols{$symbol}))) {
            $AllUnusedSymbols{$symbol}=$unused_parameters;
        }
    }
    else {
        if (scalar(@fields) > 0) {
            if (! exists ($AllIncompleteSymbols{$symbol})) {
                $AllIncompleteSymbols{$symbol}="<items>";
                &LogWarning (&GetSymbolSourceFile ($symbol), &GetSymbolSourceLine($symbol),
                    "Field descriptions for union $symbol are missing in source code comment block.");
                #("Remaining union fields: ".@fields.":".join(',',@fields)."\n");
            }
        }
    }

    $desc .= OutputSymbolTraits ($symbol);
    $desc .= "</refsect2>\n";
    return ($synop, $desc);
}


#############################################################################
# Function    : OutputEnum
# Description : Returns the synopsis and detailed description of a enum.
# Arguments   : $symbol - the enum.
#                $declaration - the declaration of the enum.
#############################################################################

sub OutputEnum {
    my ($symbol, $declaration) = @_;

    my $is_gtype = 0;
    if (&CheckIsObject ($symbol)) {
        #("Found enum gtype: $symbol\n");
        $is_gtype = 1;
    }

    my $id;
    my $condition;
    if ($is_gtype) {
        $id = &CreateValidSGMLID ($symbol . "_enum");
        $condition = &MakeConditionDescription ($symbol . "_enum");
    } else {
        $id = &CreateValidSGMLID ($symbol);
        $condition = &MakeConditionDescription ($symbol);
    }

    my $synop = "<row><entry role=\"datatype_keyword\">enum</entry><entry role=\"function_name\"><link linkend=\"$id\">$symbol</link></entry></row>\n";
    my $desc = "<refsect2 id=\"$id\" role=\"enum\"$condition>\n<title>enum $symbol</title>\n";

    $desc .= MakeIndexterms($symbol, $id);
    $desc .= "\n";
    $desc .= OutputSymbolExtraLinks($symbol);
    $desc .= &MakeDeprecationNote($symbol);

    if (defined ($SymbolDocs{$symbol})) {
        $desc .= &ConvertMarkDown($symbol, $SymbolDocs{$symbol});
    }

    # Create a table of fields and descriptions

    my @fields = ParseEnumDeclaration($declaration);
    my $params = $SymbolParams{$symbol};

    # If nothing at all is documented log a single summary warning at the end.
    # Otherwise, warn about each undocumented item.

    my $found = 0;
    if (defined $params) {
        for (my $i = 1; $i <= $#$params; $i += $PARAM_FIELD_COUNT) {
            if ($params->[$i] =~ /\S/) {
                $found = 1;
                last;
            }
        }
    }

    my %field_descrs = (defined $params ? @$params : ());
    my $missing_parameters = "";
    my $unused_parameters = "";

    $id = &CreateValidSGMLID ("$symbol".".members");
    $desc .= <<EOF;
<refsect3 id="$id" role="enum_members">\n<title>Members</title>
<informaltable role="enum_members_table" pgwide="1" frame="none">
<tgroup cols="3">
<colspec colname="enum_members_name" colwidth="300px"/>
<colspec colname="enum_members_description"/>
<colspec colname="enum_members_annotations" colwidth="200px"/>
<tbody>
EOF

    for my $field_name (@fields) {
        my $field_descr = $field_descrs{$field_name};
        my $param_annotations = "";

        $id = &CreateValidSGMLID ($field_name);
        $condition = &MakeConditionDescription ($field_name);
        $desc .= "<row role=\"constant\"><entry role=\"enum_member_name\"><para id=\"$id\">$field_name</para></entry>\n";
        if (defined $field_descr) {
            ($field_descr,$param_annotations) = &ExpandAnnotation($symbol, $field_descr);
            $field_descr = &ConvertMarkDown($symbol, $field_descr);
            $desc .= "<entry role=\"enum_member_description\">$field_descr</entry>\n<entry role=\"enum_member_annotations\">$param_annotations</entry>\n";
            delete $field_descrs{$field_name};
        } else {
            if ($found) {
                &LogWarning (&GetSymbolSourceFile ($symbol), &GetSymbolSourceLine($symbol),
                    "Value description for $symbol"."::"."$field_name is missing in source code comment block.");
                if ($missing_parameters ne "") {
                    $missing_parameters .= ", ".$field_name;
                } else {
                    $missing_parameters = $field_name;
                }
            }
            $desc .= "<entry /><entry />\n";
        }
        $desc .= "</row>\n";
    }
    $desc .= "</tbody></tgroup></informaltable>\n</refsect3>";
    foreach my $field_name (keys %field_descrs) {
        &LogWarning (&GetSymbolSourceFile ($symbol), &GetSymbolSourceLine($symbol),
            "Value description for $symbol"."::"."$field_name is not used from source code comment block.");
        if ($unused_parameters ne "") {
            $unused_parameters .= ", ".$field_name;
        } else {
            $unused_parameters = $field_name;
        }
    }

    # remember missing/unused parameters (needed in tmpl-free build)
    if (($missing_parameters ne "") and (! exists ($AllIncompleteSymbols{$symbol}))) {
        $AllIncompleteSymbols{$symbol}=$missing_parameters;
    }
    if (($unused_parameters ne "") and (! exists ($AllUnusedSymbols{$symbol}))) {
        $AllUnusedSymbols{$symbol}=$unused_parameters;
    }

    if (!$found) {
        if (scalar(@fields) > 0) {
            if (! exists ($AllIncompleteSymbols{$symbol})) {
                $AllIncompleteSymbols{$symbol}="<items>";
                &LogWarning (&GetSymbolSourceFile ($symbol), &GetSymbolSourceLine($symbol),
                    "Value descriptions for $symbol are missing in source code comment block.");
            }
        }
    }

    $desc .= OutputSymbolTraits ($symbol);
    $desc .= "</refsect2>\n";
    return ($synop, $desc);
}


#############################################################################
# Function    : OutputVariable
# Description : Returns the synopsis and detailed description of a variable.
# Arguments   : $symbol - the extern'ed variable.
#                $declaration - the declaration of the variable.
#############################################################################

sub OutputVariable {
    my ($symbol, $declaration) = @_;
    my $id = &CreateValidSGMLID ($symbol);
    my $condition = &MakeConditionDescription ($symbol);
    
    #("ouputing variable: '$symbol' '$declaration'");

    my $type_output;
    if ($declaration =~ m/^\s*extern\s+((const\s+|signed\s+|unsigned\s+|long\s+|short\s+)*\w+)(\s+\*+|\*+|\s)(\s*)(const\s+)*([A-Za-z]\w*)\s*;/) {
        my $mod1 = defined ($1) ? $1 : "";
        my $ptr = defined ($3) ? $3 : "";
        my $space = defined ($4) ? $4 : "";
        my $mod2 = defined ($5) ? $5 : "";
        $type_output = "extern $mod1$ptr$space$mod2";
    } elsif ($declaration =~ m/^\s*((const\s+|signed\s+|unsigned\s+|long\s+|short\s+)*\w+)(\s+\*+|\*+|\s)(\s*)(const\s+)*([A-Za-z]\w*)\s*=/) {
        my $mod1 = defined ($1) ? $1 : "";
        my $ptr = defined ($3) ? $3 : "";
        my $space = defined ($4) ? $4 : "";
        my $mod2 = defined ($5) ? $5 : "";
        $type_output = "$mod1$ptr$space$mod2";
    } else {
        $type_output = "extern";
    }
    my $synop = "<row><entry role=\"variable_type\">${type_output}</entry><entry role=\"function_name\"><link linkend=\"$id\">$symbol</link></entry></row>\n";

    my $desc = "<refsect2 id=\"$id\" role=\"variable\"$condition>\n<title>$symbol</title>\n";

    $desc .= MakeIndexterms($symbol, $id);
    $desc .= "\n";
    $desc .= OutputSymbolExtraLinks($symbol);

    my $decl_out = &CreateValidSGML ($declaration);
    $desc .= "<programlisting language=\"C\">$decl_out</programlisting>\n";

    $desc .= &MakeDeprecationNote($symbol);

    if (defined ($SymbolDocs{$symbol})) {
        $desc .= &ConvertMarkDown($symbol, $SymbolDocs{$symbol});
    }
    if (defined ($SymbolAnnotations{$symbol})) {
        my $param_desc = $SymbolAnnotations{$symbol};
        my $param_annotations = "";
        ($param_desc,$param_annotations) = &ExpandAnnotation($symbol, $param_desc);
        if ($param_annotations ne "") {
            $desc .= "\n<para>$param_annotations</para>";
        }
    }

    $desc .= OutputSymbolTraits ($symbol);
    $desc .= "</refsect2>\n";
    return ($synop, $desc);
}


#############################################################################
# Function    : OutputFunction
# Description : Returns the synopsis and detailed description of a function.
# Arguments   : $symbol - the function.
#                $declaration - the declaration of the function.
#############################################################################

sub OutputFunction {
    my ($symbol, $declaration, $symbol_type) = @_;
    my $id = &CreateValidSGMLID ($symbol);
    my $condition = &MakeConditionDescription ($symbol);

    # Take out the return type     $1                                                                                       $2   $3
    $declaration =~ s/<RETURNS>\s*((?:const\s+|G_CONST_RETURN\s+|signed\s+|unsigned\s+|long\s+|short\s+|struct\s+|enum\s+)*)(\w+)(\s*\**\s*(?:const|G_CONST_RETURN)?\s*\**\s*(?:restrict)?\s*)<\/RETURNS>\n//;
    my $type_modifier = defined($1) ? $1 : "";
    my $type = $2;
    my $pointer = $3;
    # Trim trailing spaces as we are going to pad to $RETURN_TYPE_FIELD_WIDTH below anyway
    $pointer =~ s/\s+$//;
    my $xref = &MakeXRef ($type, &tagify($type, "returnvalue"));
    my $start = "";
    #if ($symbol_type eq 'USER_FUNCTION') {
    #    $start = "typedef ";
    #}

    # We output const rather than G_CONST_RETURN.
    $type_modifier =~ s/G_CONST_RETURN/const/g;
    $pointer =~ s/G_CONST_RETURN/const/g;
    $pointer =~ s/^\s+/&#160;/g;

    my $ret_type_output;
    $ret_type_output = "$start$type_modifier$xref$pointer\n";

    my $indent_len;
    $indent_len = length ($symbol) + 2;
    my $char1 = my $char2 = my $char3 = "";
    if ($symbol_type eq 'USER_FUNCTION') {
        $indent_len += 3;
        $char1 = "<phrase role=\"c_punctuation\">(</phrase>";
        $char2 = "*";
        $char3 = "<phrase role=\"c_punctuation\">)</phrase>";
    }

    my ($symbol_output, $symbol_desc_output);
    $symbol_output = "$char1<link linkend=\"$id\">$char2$symbol</link>$char3";
    if ($indent_len < $MAX_SYMBOL_FIELD_WIDTH) {
        $symbol_desc_output = "$char1$char2$symbol$char3 ";
    } else {
        $indent_len = $MAX_SYMBOL_FIELD_WIDTH - 8;
        $symbol_desc_output = "$char1$char2$symbol$char3\n"
          . (' ' x ($indent_len - 1));
    }

    my $synop = "<row><entry role=\"function_type\">${ret_type_output}</entry><entry role=\"function_name\">${symbol_output}&#160;<phrase role=\"c_punctuation\">()</phrase></entry></row>\n";

    my $desc = "<refsect2 id=\"$id\" role=\"function\"$condition>\n<title>${symbol}&#160;()</title>\n";

    $desc .= MakeIndexterms($symbol, $id);
    $desc .= "\n";
    $desc .= OutputSymbolExtraLinks($symbol);

    $desc  .= "<programlisting language=\"C\">${ret_type_output}$symbol_desc_output(";

    my @fields = ParseFunctionDeclaration($declaration, \&MakeXRef,
                                        sub {
                                            &tagify($_[0],"parameter");
                                        });

    for (my $i = 1; $i <= $#fields; $i += 2) {
        my $field_name = $fields[$i];

        if ($i == 1) {
            $desc  .= "$field_name";
        } else {
            $desc  .= ",\n"
                . (' ' x $indent_len)
                . "$field_name";
        }

    }

    $desc  .= ");</programlisting>\n";

    $desc .= &MakeDeprecationNote($symbol);

    if (defined ($SymbolDocs{$symbol})) {
        $desc .= &ConvertMarkDown($symbol, $SymbolDocs{$symbol});
    }
    if (defined ($SymbolAnnotations{$symbol})) {
        my $param_desc = $SymbolAnnotations{$symbol};
        my $param_annotations = "";
        ($param_desc,$param_annotations) = &ExpandAnnotation($symbol, $param_desc);
        if ($param_annotations ne "") {
            $desc .= "\n<para>$param_annotations</para>";
        }
    }

    $desc .= &OutputParamDescriptions ("FUNCTION", $symbol, @fields);
    $desc .= OutputSymbolTraits ($symbol);
    $desc .= "</refsect2>\n";
    return ($synop, $desc);
}


#############################################################################
# Function    : OutputParamDescriptions
# Description : Returns the DocBook output describing the parameters of a
#                function, macro or signal handler.
# Arguments   : $symbol_type - 'FUNCTION', 'MACRO' or 'SIGNAL'. Signal
#                  handlers have an implicit user_data parameter last.
#                $symbol - the name of the function/macro being described.
#               @fields - parsed fields from the declaration, used to determine
#                  undocumented/unused entries
#############################################################################

sub OutputParamDescriptions {
    my ($symbol_type, $symbol, @fields) = @_;
    my $output = "";
    my $params = $SymbolParams{$symbol};
    my $num_params = 0;
    my %field_descrs = ();

    if (@fields) {
        %field_descrs = @fields;
        delete $field_descrs{"void"};
        delete $field_descrs{"Returns"};
    }

    if (defined $params) {
        my $returns = "";
        my $params_desc = "";
        my $missing_parameters = "";
        my $unused_parameters = "";
        my $j;

        for ($j = 0; $j <= $#$params; $j += $PARAM_FIELD_COUNT) {
            my $param_name = $$params[$j];
            my $param_desc = $$params[$j + 1];
            my $param_annotations = "";

            ($param_desc,$param_annotations) = &ExpandAnnotation($symbol, $param_desc);
            $param_desc = &ConvertMarkDown($symbol, $param_desc);
            # trim
            $param_desc =~ s/^(\s|\n)+//msg;
            $param_desc =~ s/(\s|\n)+$//msg;
            if ($param_name eq "Returns") {
                $returns = $param_desc;
                if ($param_annotations ne "") {
                    $returns .= "\n<para>$param_annotations</para>";
                }
            } elsif ($param_name eq "void") {
                # FIXME: &LogWarning()?
                #("!!!! void in params for $symbol?\n");
            } else {
                if (@fields) {
                    if (!defined $field_descrs{$param_name}) {
                        &LogWarning (&GetSymbolSourceFile ($symbol), &GetSymbolSourceLine($symbol),
                            "Parameter description for $symbol"."::"."$param_name is not used from source code comment block.");
                        if ($unused_parameters ne "") {
                          $unused_parameters .= ", ".$param_name;
                        } else {
                           $unused_parameters = $param_name;
                        }
                    } else {
                        delete $field_descrs{$param_name};
                    }
                }
                if($param_desc ne "") {
                    $params_desc .= "<row><entry role=\"parameter_name\"><para>$param_name</para></entry>\n<entry role=\"parameter_description\">$param_desc</entry>\n<entry role=\"parameter_annotations\">$param_annotations</entry></row>\n";
                    $num_params++;
                }
            }
        }
        foreach my $param_name (keys %field_descrs) {
            &LogWarning (&GetSymbolSourceFile ($symbol), &GetSymbolSourceLine($symbol),
                "Parameter description for $symbol"."::"."$param_name is missing in source code comment block.");
            if ($missing_parameters ne "") {
              $missing_parameters .= ", ".$param_name;
            } else {
               $missing_parameters = $param_name;
            }
        }

        # Signals have an implicit user_data parameter which we describe.
        if ($symbol_type eq "SIGNAL") {
            $params_desc .= "<row><entry role=\"parameter_name\"><simpara>user_data</simpara></entry>\n<entry role=\"parameter_description\"><simpara>user data set when the signal handler was connected.</simpara></entry>\n<entry role=\"parameter_annotations\"></entry></row>\n";
        }

        # Start a table if we need one.
        if ($params_desc ne "") {
          my $id = &CreateValidSGMLID ("$symbol".".parameters");

          $output .= <<EOF;
<refsect3 id="$id" role="parameters">\n<title>Parameters</title>
<informaltable role="parameters_table" pgwide="1" frame="none">
<tgroup cols="3">
<colspec colname="parameters_name" colwidth="150px"/>
<colspec colname="parameters_description"/>
<colspec colname="parameters_annotations" colwidth="200px"/>
<tbody>
EOF
          $output .= $params_desc;
          $output .= "</tbody></tgroup></informaltable>\n</refsect3>";
        }

        # Output the returns info last
        if ($returns ne "") {
          my $id = &CreateValidSGMLID ("$symbol".".returns");

          $output .= <<EOF;
<refsect3 id="$id" role=\"returns\">\n<title>Returns</title>
EOF
          $output .= $returns;
          $output .= "\n</refsect3>";
        }

        # remember missing/unused parameters (needed in tmpl-free build)
        if (($missing_parameters ne "") and (! exists ($AllIncompleteSymbols{$symbol}))) {
            $AllIncompleteSymbols{$symbol}=$missing_parameters;
        }
        if (($unused_parameters ne "") and (! exists ($AllUnusedSymbols{$symbol}))) {
            $AllUnusedSymbols{$symbol}=$unused_parameters;
        }
    }
    if (($num_params == 0) && @fields && (scalar(keys(%field_descrs)) > 0)) {
        if (! exists ($AllIncompleteSymbols{$symbol})) {
            $AllIncompleteSymbols{$symbol}="<parameters>";
        }
    }

    return $output;
}


#############################################################################
# Function    : ParseStabilityLevel
# Description : Parses a stability level and outputs a warning if it isn't
#               valid.
# Arguments   : $stability - the stability text.
#                $file, $line - context for error message
#                $message - description of where the level is from, to use in
#               any error message.
# Returns     : The parsed stability level string.
#############################################################################

sub ParseStabilityLevel {
    my ($stability, $file, $line, $message) = @_;

    $stability =~ s/^\s*//;
    $stability =~ s/\s*$//;
    if ($stability =~ m/^stable$/i) {
        $stability = "Stable";
    } elsif ($stability =~ m/^unstable$/i) {
        $stability = "Unstable";
    } elsif ($stability =~ m/^private$/i) {
        $stability = "Private";
    } else {
        &LogWarning ($file, $line, "$message is $stability.".
            "It should be one of these: Stable, Unstable, or Private.");
    }
    return $stability;
}


#############################################################################
# Function    : OutputDBFile
# Description : Outputs the final DocBook file for one section.
# Arguments   : $file - the name of the file.
#               $title - the title from the $MODULE-sections.txt file, which
#                 will be overridden by the title in the template file.
#               $section_id - the id to use for the toplevel tag.
#               $includes - comma-separates list of include files added at top of
#                 synopsis, with '<' '>' around them (if not already enclosed in "").
#               $functions_synop - reference to the DocBook for the Functions Synopsis part.
#               $other_synop - reference to the DocBook for the Types and Values Synopsis part.
#               $functions_details - reference to the DocBook for the Functions Details part.
#               $other_details - reference to the DocBook for the Types and Values Details part.
#               $signal_synop - reference to the DocBook for the Signal Synopsis part
#               $signal_desc - reference to the DocBook for the Signal Description part
#               $args_synop - reference to the DocBook for the Arg Synopsis part
#               $args_desc - reference to the DocBook for the Arg Description part
#               $hierarchy - reference to the DocBook for the Object Hierarchy part
#               $interfaces - reference to the DocBook for the Interfaces part
#               $implementations - reference to the DocBook for the Known Implementations part
#               $prerequisites - reference to the DocBook for the Prerequisites part
#               $derived - reference to the DocBook for the Derived Interfaces part
#               $file_objects - reference to an array of objects in this file
#############################################################################

sub OutputDBFile {
    my ($file, $title, $section_id, $includes, $functions_synop, $other_synop, $functions_details, $other_details, $signals_synop, $signals_desc, $args_synop, $args_desc, $hierarchy, $interfaces, $implementations, $prerequisites, $derived, $file_objects) = @_;

    #("Output docbook for file $file with title '$title'\n");

    # The edited title overrides the one from the sections file.
    my $new_title = $SymbolDocs{"$TMPL_DIR/$file:Title"};
    if (defined ($new_title) && $new_title !~ m/^\s*$/) {
        $title = $new_title;
        #("Found title: $title\n");
    }
    my $short_desc = $SymbolDocs{"$TMPL_DIR/$file:Short_Description"};
    if (!defined ($short_desc) || $short_desc =~ m/^\s*$/) {
        $short_desc = "";
    } else {
        # Don't use ConvertMarkDown here for now since we don't want blocks
        $short_desc = &ExpandAbbreviations("$title:Short_description",
                                           $short_desc);
        #("Found short_desc: $short_desc");
    }
    my $long_desc = $SymbolDocs{"$TMPL_DIR/$file:Long_Description"};
    if (!defined ($long_desc) || $long_desc =~ m/^\s*$/) {
        $long_desc = "";
    } else {
        $long_desc = &ConvertMarkDown("$title:Long_description",
                                          $long_desc);
        #("Found long_desc: $long_desc");
    }
    my $see_also = $SymbolDocs{"$TMPL_DIR/$file:See_Also"};
    if (!defined ($see_also) || $see_also =~ m%^\s*(<para>)?\s*(</para>)?\s*$%) {
        $see_also = "";
    } else {
        $see_also = &ConvertMarkDown("$title:See_Also", $see_also);
        #("Found see_also: $see_also");
    }
    if ($see_also) {
        $see_also = "<refsect1 id=\"$section_id.see-also\">\n<title>See Also</title>\n$see_also\n</refsect1>\n";
    }
    my $stability = $SymbolDocs{"$TMPL_DIR/$file:Stability_Level"};
    if (!defined ($stability) || $stability =~ m/^\s*$/) {
        $stability = "";
    } else {
        $stability = &ParseStabilityLevel($stability, $file, $., "Section stability level");
        #("Found stability: $stability");
    }
    if ($stability) {
        $AnnotationsUsed{$stability} = 1;
        $stability = "<refsect1 id=\"$section_id.stability-level\">\n<title>Stability Level</title>\n<acronym>$stability</acronym>, unless otherwise indicated\n</refsect1>\n";
    } elsif ($DEFAULT_STABILITY) {
        $AnnotationsUsed{$DEFAULT_STABILITY} = 1;
        $stability = "<refsect1 id=\"$section_id.stability-level\">\n<title>Stability Level</title>\n<acronym>$DEFAULT_STABILITY</acronym>, unless otherwise indicated\n</refsect1>\n";
    }

    my $image = $SymbolDocs{"$TMPL_DIR/$file:Image"};
    if (!defined ($image) || $image =~ m/^\s*$/) {
      $image = "";
    } else {
      $image =~ s/^\s*//;
      $image =~ s/\s*$//;

      my $format;

      if ($image =~ /jpe?g$/i) {
        $format = "format='JPEG'";
      } elsif ($image =~ /png$/i) {
        $format = "format='PNG'";
      } elsif ($image =~ /svg$/i) {
        $format = "format='SVG'";
      } else {
        $format = "";
      }

      $image = "  <inlinegraphic fileref='$image' $format/>\n"
    }

    my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) =
        gmtime (time);
    my $month = (qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec))[$mon];
    $year += 1900;

    my $include_output = "";
    if ($includes) {
      $include_output .= "<refsect1 id=\"$section_id.includes\"><title>Includes</title><synopsis>";
      my $include;
      foreach $include (split (/,/, $includes)) {
        if ($include =~ m/^\".+\"$/) {
          $include_output .= "#include ${include}\n";
        }
        else {
          $include =~ s/^\s+|\s+$//gs;
          $include_output .= "#include &lt;${include}&gt;\n";
        }
      }
      $include_output .= "</synopsis></refsect1>\n";
    }

    my $extralinks = OutputSectionExtraLinks($title,"Section:$file");

    my $old_db_file = "$DB_OUTPUT_DIR/$file.xml";
    my $new_db_file = "$DB_OUTPUT_DIR/$file.xml.new";

    open (OUTPUT, ">$new_db_file")
        || die "Can't create $new_db_file: $!";

    my $object_anchors = "";
    foreach my $object (@$file_objects) {
        next if ($object eq $section_id);
        my $id = CreateValidSGMLID($object);
        #("Adding anchor for $object\n");
        $object_anchors .= "<anchor id=\"$id\"/>";
    }

    # Make sure we produce valid docbook
    $$functions_details ||= "<para />";

    # We used to output this, but is messes up our UpdateFileIfChanged code
    # since it changes every day (and it is only used in the man pages):
    # "<refentry id="$section_id" revision="$mday $month $year">"

    print OUTPUT <<EOF;
${\( MakeDocHeader ("refentry") )}
<refentry id="$section_id">
<refmeta>
<refentrytitle role="top_of_page" id="$section_id.top_of_page">$title</refentrytitle>
<manvolnum>3</manvolnum>
<refmiscinfo>\U$MODULE\E Library$image</refmiscinfo>
</refmeta>
<refnamediv>
<refname>$title</refname>
<refpurpose>$short_desc</refpurpose>
</refnamediv>
$stability
$$functions_synop$$args_synop$$signals_synop$object_anchors$$other_synop$$hierarchy$$prerequisites$$derived$$interfaces$$implementations
$include_output
<refsect1 id="$section_id.description" role="desc">
<title role="desc.title">Description</title>
$extralinks$long_desc
</refsect1>
<refsect1 id="$section_id.functions_details" role="details">
<title role="details.title">Functions</title>
$$functions_details
</refsect1>
<refsect1 id="$section_id.other_details" role="details">
<title role="details.title">Types and Values</title>
$$other_details
</refsect1>
$$args_desc$$signals_desc$see_also
</refentry>
EOF
    close (OUTPUT);

    return &UpdateFileIfChanged ($old_db_file, $new_db_file, 0);
}


#############################################################################
# Function    : OutputExtraFile
# Description : Copies an "extra" DocBook file into the output directory,
#               expanding abbreviations
# Arguments   : $file - the source file.
#############################################################################
sub OutputExtraFile {
    my ($file) = @_;

    my $basename;

    ($basename = $file) =~ s!^.*/!!;

    my $old_db_file = "$DB_OUTPUT_DIR/$basename";
    my $new_db_file = "$DB_OUTPUT_DIR/$basename.new";

    my $contents;

    open(EXTRA_FILE, "<$file") || die "Can't open $file";

    {
        local $/;
        $contents = <EXTRA_FILE>;
    }

    open (OUTPUT, ">$new_db_file")
        || die "Can't create $new_db_file: $!";

    print OUTPUT &ExpandAbbreviations ("$basename file", $contents);
    close (OUTPUT);

    return &UpdateFileIfChanged ($old_db_file, $new_db_file, 0);
}
#############################################################################
# Function    : OutputBook
# Description : Outputs the entities that need to be included into the
#                main docbook file for the module.
# Arguments   : $book_top - the declarations of the entities, which are added
#                  at the top of the main docbook file.
#                $book_bottom - the references to the entities, which are
#                  added in the main docbook file at the desired position.
#############################################################################

sub OutputBook {
    my ($book_top, $book_bottom) = @_;

    my $old_file = "$DB_OUTPUT_DIR/$MODULE-doc.top";
    my $new_file = "$DB_OUTPUT_DIR/$MODULE-doc.top.new";

    open (OUTPUT, ">$new_file")
        || die "Can't create $new_file: $!";
    print OUTPUT $book_top;
    close (OUTPUT);

    &UpdateFileIfChanged ($old_file, $new_file, 0);


    $old_file = "$DB_OUTPUT_DIR/$MODULE-doc.bottom";
    $new_file = "$DB_OUTPUT_DIR/$MODULE-doc.bottom.new";

    open (OUTPUT, ">$new_file")
        || die "Can't create $new_file: $!";
    print OUTPUT $book_bottom;
    close (OUTPUT);

    &UpdateFileIfChanged ($old_file, $new_file, 0);


    # If the main docbook file hasn't been created yet, we create it here.
    # The user can tweak it later.
    if ($MAIN_SGML_FILE && ! -e $MAIN_SGML_FILE) {
        open (OUTPUT, ">$MAIN_SGML_FILE")
          || die "Can't create $MAIN_SGML_FILE: $!";
          
        print OUTPUT <<EOF;
${\( MakeDocHeader ("book") )}
<book id="index">
  <bookinfo>
    <title>&package_name; Reference Manual</title>
    <releaseinfo>
      for &package_string;.
      The latest version of this documentation can be found on-line at
      <ulink role="online-location" url="http://[SERVER]/&package_name;/index.html">http://[SERVER]/&package_name;/</ulink>.
    </releaseinfo>
  </bookinfo>

  <chapter>
    <title>[Insert title here]</title>
    $book_bottom
  </chapter>
EOF
        if (-e $OBJECT_TREE_FILE) {
            print OUTPUT <<EOF;
  <chapter id="object-tree">
    <title>Object Hierarchy</title>
    <xi:include href="xml/tree_index.sgml"/>
  </chapter>
EOF
        } else {
            print OUTPUT <<EOF;
  <!-- enable this when you use gobject types
  <chapter id="object-tree">
    <title>Object Hierarchy</title>
    <xi:include href="xml/tree_index.sgml"/>
  </chapter>
  -->
EOF
        }
        print OUTPUT <<EOF;
  <index id="api-index-full">
    <title>API Index</title>
    <xi:include href="xml/api-index-full.xml"><xi:fallback /></xi:include>
  </index>
  <index id="deprecated-api-index" role="deprecated">
    <title>Index of deprecated API</title>
    <xi:include href="xml/api-index-deprecated.xml"><xi:fallback /></xi:include>
  </index>
EOF
        if (keys(%AnnotationsUsed)) {
            print OUTPUT <<EOF;
  <xi:include href="xml/annotation-glossary.xml"><xi:fallback /></xi:include>
EOF
        } else {
            print OUTPUT <<EOF;
  <!-- enable this when you use gobject introspection annotations
  <xi:include href="xml/annotation-glossary.xml"><xi:fallback /></xi:include>
  -->
EOF
        }
        print OUTPUT <<EOF;
</book>
EOF

        close (OUTPUT);
    }
}


#############################################################################
# Function    : CreateValidSGML
# Description : This turns any chars which are used in SGML into entities,
#                e.g. '<' into '&lt;'
# Arguments   : $text - the text to turn into proper SGML.
#############################################################################

sub CreateValidSGML {
    my ($text) = @_;
    $text =~ s/&/&amp;/g;        # Do this first, or the others get messed up.
    $text =~ s/</&lt;/g;
    $text =~ s/>/&gt;/g;
    # browers render single tabs inconsistently
    $text =~ s/([^\s])\t([^\s])/$1&#160;$2/g;
    return $text;
}

#############################################################################
# Function    : ConvertSGMLChars
# Description : This is used for text in source code comment blocks, to turn
#               chars which are used in SGML into entities, e.g. '<' into
#               '&lt;'. Depending on $INLINE_MARKUP_MODE, this is done
#               unconditionally or only if the character doesn't seem to be
#               part of an SGML construct (tag or entity reference).
# Arguments   : $text - the text to turn into proper SGML.
#############################################################################

sub ConvertSGMLChars {
    my ($symbol, $text) = @_;

    if ($INLINE_MARKUP_MODE) {
        # For the XML/SGML mode only convert to entities outside CDATA sections.
        return &ModifyXMLElements ($text, $symbol,
                                   "<!\\[CDATA\\[|<programlisting[^>]*>",
                                   \&ConvertSGMLCharsEndTag,
                                   \&ConvertSGMLCharsCallback);
    } else {
        # For the simple non-sgml mode, convert to entities everywhere.

	# First, convert freestanding & to &amp;
        $text =~ s/&(?![a-zA-Z#]+;)/&amp;/g;
        $text =~ s/</&lt;/g;
        # Allow ">" at beginning of string for blockquote markdown
        $text =~ s/(?<=[^\w\n"'\/-])>/&gt;/g;

        return $text;
    }
}


sub ConvertSGMLCharsEndTag {
  if ($_[0] eq "<!\[CDATA\[") {
    return "]]>";
  } else {
    return "</programlisting>";
  }
}

sub ConvertSGMLCharsCallback {
  my ($text, $symbol, $tag) = @_;

  if ($tag =~ m/^<programlisting/) {
    # We can handle <programlisting> specially here.
    return &ModifyXMLElements ($text, $symbol,
                               "<!\\[CDATA\\[",
                               \&ConvertSGMLCharsEndTag,
                               \&ConvertSGMLCharsCallback2);
  } elsif ($tag eq "") {
    # If we're not in CDATA convert to entities.
    $text =~ s/&(?![a-zA-Z#]+;)/&amp;/g;        # Do this first, or the others get messed up.
    $text =~ s/<(?![a-zA-Z\/!])/&lt;/g;
    # Allow ">" at beginning of string for blockquote markdown
    $text =~ s/(?<=[^\w\n"'\/-])>/&gt;/g;

    # Handle "#include <xxxxx>"
    $text =~ s/#include(\s+)<([^>]+)>/#include$1&lt;$2&gt;/g;
  }

  return $text;
}

sub ConvertSGMLCharsCallback2 {
  my ($text, $symbol, $tag) = @_;

  # If we're not in CDATA convert to entities.
  # We could handle <programlisting> differently, though I'm not sure it helps.
  if ($tag eq "") {
    # replace only if its not a tag
    $text =~ s/&(?![a-zA-Z#]+;)/&amp;/g;        # Do this first, or the others get messed up.
    $text =~ s/<(?![a-zA-Z\/!])/&lt;/g;
    $text =~ s/(?<![a-zA-Z0-9"'\/-])>/&gt;/g;

    # Handle "#include <xxxxx>"
    $text =~ s/#include(\s+)<([^>]+)>/#include$1&lt;$2&gt;/g;
  }

  return $text;
}

#############################################################################
# Function    : ExpandAnnotation
# Description : This turns annotations into acronym tags.
# Arguments   : $symbol - the symbol being documented, for error messages.
#                $text - the text to expand.
#############################################################################
sub ExpandAnnotation {
    my ($symbol, $param_desc) = @_;
    my $param_annotations = "";

    # look for annotations at the start of the comment part
    # function level annotations don't end with a colon ':'
    if ($param_desc =~ m%^\s*\((.*?)\)(:|$)%) {
        my @annotations;
        my $annotation;
        $param_desc = $';

        @annotations = split(/\)\s*\(/,$1);
        #("annotations for $symbol: '$1'\n");
        foreach $annotation (@annotations) {
            # need to search for the longest key-match in %AnnotationDefinition
            my $match_length=0;
            my $match_annotation="";
            my $annotationdef;
            foreach $annotationdef (keys %AnnotationDefinition) {
                if ($annotation =~ m/^$annotationdef/) {
                    if (length($annotationdef)>$match_length) {
                        $match_length=length($annotationdef);
                        $match_annotation=$annotationdef;
                    }
                }
            }
            my $annotation_extra = "";
            if ($match_annotation ne "") {
                if ($annotation =~ m%$match_annotation\s+(.*)%) {
                    $annotation_extra = " $1";
                }
                $AnnotationsUsed{$match_annotation} = 1;
                $param_annotations .= "[<acronym>$match_annotation</acronym>$annotation_extra]";
            }
            else {
                &LogWarning (&GetSymbolSourceFile ($symbol), &GetSymbolSourceLine($symbol),
                    "unknown annotation \"$annotation\" in documentation for $symbol.");
                $param_annotations .= "[$annotation]";
            }
        }
        chomp($param_desc);
        $param_desc =~ m/^(.*?)\.*\s*$/s;
        $param_desc = "$1. ";
    }
    if ($param_annotations ne "") {
        $param_annotations = "<emphasis role=\"annotation\">$param_annotations</emphasis>";
    }
    return ($param_desc, $param_annotations);
}

#############################################################################
# Function    : ExpandAbbreviations
# Description : This turns the abbreviations function(), macro(), @param,
#                %constant, and #symbol into appropriate DocBook markup.
#               CDATA sections and <programlisting> parts are skipped.
# Arguments   : $symbol - the symbol being documented, for error messages.
#                $text - the text to expand.
#############################################################################

sub ExpandAbbreviations {
  my ($symbol, $text) = @_;

  # Note: This is a fallback and normally done in the markdown parser

  # Convert "|[" and "]|" into the start and end of program listing examples.
  # Support \[<!-- language="C" --> modifiers
  $text =~ s%\|\[<!-- language="([^"]+)" -->%<informalexample><programlisting language="$1"><![CDATA[%g;
  $text =~ s%\|\[%<informalexample><programlisting><![CDATA[%g;
  $text =~ s%\]\|%]]></programlisting></informalexample>%g;

  # keep CDATA unmodified, preserve ulink tags (ideally we preseve all tags
  # as such)
  return &ModifyXMLElements ($text, $symbol,
                             "<!\\[CDATA\\[|<ulink[^>]*>|<programlisting[^>]*>|<!DOCTYPE",
                             \&ExpandAbbreviationsEndTag,
                             \&ExpandAbbreviationsCallback);
}


# Returns the end tag (as a regexp) corresponding to the given start tag.
sub ExpandAbbreviationsEndTag {
  my ($start_tag) = @_;

  if ($start_tag eq "<!\[CDATA\[") {
    return "]]>";
  } elsif ($start_tag eq "<!DOCTYPE") {
    return ">";
  } elsif ($start_tag =~ m/<(\w+)/) {
    return "</$1>";
  }
}

# Called inside or outside each CDATA or <programlisting> section.
sub ExpandAbbreviationsCallback {
  my ($text, $symbol, $tag) = @_;

  if ($tag =~ m/^<programlisting/) {
    # Handle any embedded CDATA sections.
    return &ModifyXMLElements ($text, $symbol,
                               "<!\\[CDATA\\[",
                               \&ExpandAbbreviationsEndTag,
                               \&ExpandAbbreviationsCallback2);
  } elsif ($tag eq "") {
    # NOTE: this is a fallback. It is normally done by the Markdown parser.

    # We are outside any CDATA or <programlisting> sections, so we expand
    # any gtk-doc abbreviations.

    # Convert '@param()'
    # FIXME: we could make those also links ($symbol.$2), but that would be less
    # useful as the link target is a few lines up or down
    $text =~ s/(\A|[^\\])\@(\w+((\.|->)\w+)*)\s*\(\)/$1<parameter>$2()<\/parameter>/g;

    # Convert 'function()' or 'macro()'.
    # if there is abc_*_def() we don't want to make a link to _def()
    # FIXME: also handle abc(def(....)) : but that would need to be done recursively :/
    $text =~ s/([^\*.\w])(\w+)\s*\(\)/$1.&MakeXRef($2, &tagify($2 . "()", "function"));/eg;
    # handle #Object.func()
    $text =~ s/(\A|[^\\])#([\w\-:\.]+[\w]+)\s*\(\)/$1.&MakeXRef($2, &tagify($2 . "()", "function"));/eg;

    # Convert '@param', but not '\@param'.
    $text =~ s/(\A|[^\\])\@(\w+((\.|->)\w+)*)/$1<parameter>$2<\/parameter>/g;
    $text =~ s/\\\@/\@/g;

    # Convert '%constant', but not '\%constant'.
    # Also allow negative numbers, e.g. %-1.
    $text =~ s/(\A|[^\\])\%(-?\w+)/$1.&MakeXRef($2, &tagify($2, "literal"));/eg;
    $text =~ s/\\\%/\%/g;

    # Convert '#symbol', but not '\#symbol'.
    $text =~ s/(\A|[^\\])#([\w\-:\.]+[\w]+)/$1.&MakeHashXRef($2, "type");/eg;
    $text =~ s/\\#/#/g;
  }

  return $text;
}

# This is called inside a <programlisting>
sub ExpandAbbreviationsCallback2 {
  my ($text, $symbol, $tag) = @_;

  if ($tag eq "") {
    # We are inside a <programlisting> but outside any CDATA sections,
    # so we expand any gtk-doc abbreviations.
    # FIXME: why is this different from &ExpandAbbreviationsCallback(),
    #        why not just call it
    $text =~ s/#(\w+)/&MakeHashXRef($1, "");/eg;
  } elsif ($tag eq "<![CDATA[") {
    # NOTE: this is a fallback. It is normally done by the Markdown parser.
    $text = &ReplaceEntities ($text, $symbol);
  }

  return $text;
}

sub MakeHashXRef {
    my ($symbol, $tag) = @_;;
    my $text = $symbol;

    # Check for things like '#include', '#define', and skip them.
    if ($PreProcessorDirectives{$symbol}) {
      return "#$symbol";
    }

    # Get rid of special suffixes ('-struct','-enum').
    $text =~ s/-struct$//;
    $text =~ s/-enum$//;

    # If the symbol is in the form "Object::signal", then change the symbol to
    # "Object-signal" and use "signal" as the text.
    if ($symbol =~ s/::/-/) {
      $text = "“$'”";
    }

    # If the symbol is in the form "Object:property", then change the symbol to
    # "Object--property" and use "property" as the text.
    if ($symbol =~ s/:/--/) {
      $text = "“$'”";
    }

    if ($tag ne "") {
      $text = tagify ($text, $tag);
    }

    return &MakeXRef($symbol, $text);
}


#############################################################################
# Function    : ModifyXMLElements
# Description : Looks for given XML element tags within the text, and calls
#               the callback on pieces of text inside & outside those elements.
#               Used for special handling of text inside things like CDATA
#               and <programlisting>.
# Arguments   : $text - the text.
#               $symbol - the symbol currently being documented (only used for
#                      error messages).
#               $start_tag_regexp - the regular expression to match start tags.
#                      e.g. "<!\\[CDATA\\[|<programlisting[^>]*>" to match
#                      CDATA sections or programlisting elements.
#               $end_tag_func - function which is passed the matched start tag
#                      and should return the appropriate end tag string regexp.
#               $callback - callback called with each part of the text. It is
#                      called with a piece of text, the symbol being
#                      documented, and the matched start tag or "" if the text
#                      is outside the XML elements being matched.
#############################################################################
sub ModifyXMLElements {
    my ($text, $symbol, $start_tag_regexp, $end_tag_func, $callback) = @_;
    my ($before_tag, $start_tag, $end_tag_regexp, $end_tag);
    my $result = "";

    while ($text =~ m/$start_tag_regexp/s) {
      $before_tag = $`; # Prematch for last successful match string
      $start_tag = $&;  # Last successful match
      $text = $';       # Postmatch for last successful match string

      $result .= &$callback ($before_tag, $symbol, "");
      $result .= $start_tag;

      # get the matching end-tag for current tag
      $end_tag_regexp = &$end_tag_func ($start_tag);

      if ($text =~ m/$end_tag_regexp/s) {
        $before_tag = $`;
        $end_tag = $&;
        $text = $';

        $result .= &$callback ($before_tag, $symbol, $start_tag);
        $result .= $end_tag;
      } else {
        &LogWarning (&GetSymbolSourceFile ($symbol), &GetSymbolSourceLine($symbol),
            "Can't find tag end: $end_tag_regexp in docs for: $symbol.");
        # Just assume it is all inside the tag.
        $result .= &$callback ($text, $symbol, $start_tag);
        $text = "";
      }
    }

    # Handle any remaining text outside the tags.
    $result .= &$callback ($text, $symbol, "");

    return $result;
}

sub noop {
  return $_[0];
}

# Adds a tag around some text.
# e.g tagify("Text", "literal") => "<literal>Text</literal>".
sub tagify {
   my ($text, $elem) = @_;
   return "<" . $elem . ">" . $text . "</" . $elem . ">";
}

#############################################################################
# Function    : MakeDocHeader
# Description : Builds a docbook header for the given tag
# Arguments   : $tag - doctype tag
#############################################################################

sub MakeDocHeader {
    my ($tag) = @_;
    my $header = $doctype_header;
    $header =~ s/<!DOCTYPE \w+/<!DOCTYPE $tag/;

    # fix the path for book since this is one level up
    if ($tag eq "book") {
        $header =~ s#<!ENTITY % gtkdocentities SYSTEM \"../([a-zA-Z./]+)\">#<!ENTITY % gtkdocentities SYSTEM \"$1\">#;
    }

    return $header;
}


#############################################################################
# Function    : MakeXRef
# Description : This returns a cross-reference link to the given symbol.
#                Though it doesn't try to do this for a few standard C types
#                that it        knows won't be in the documentation.
# Arguments   : $symbol - the symbol to try to create a XRef to.
#               $text - text text to put inside the XRef, defaults to $symbol
#############################################################################

sub MakeXRef {
    my ($symbol, $text) = ($_[0], $_[1]);

    $symbol =~ s/^\s+//;
    $symbol =~ s/\s+$//;

    if (!defined($text)) {
        $text = $symbol;

        # Get rid of special suffixes ('-struct','-enum').
        $text =~ s/-struct$//;
        $text =~ s/-enum$//;
    }

    if ($symbol =~ m/ /) {
        return "$text";
    }

    #("Getting type link for $symbol -> $text\n");

    my $symbol_id = &CreateValidSGMLID ($symbol);
    return "<link linkend=\"$symbol_id\">$text</link>";
}


#############################################################################
# Function    : MakeIndexterms
# Description : This returns a indexterm elements for the given symbol
# Arguments   : $symbol - the symbol to create indexterms for
#############################################################################

sub MakeIndexterms {
  my ($symbol, $id) = @_;
  my $terms =  "";
  my $sortas = "";

  # make the index useful, by ommiting the namespace when sorting
  if ($NAME_SPACE ne "") {
    if ($symbol =~ m/^$NAME_SPACE\_?(.*)/i) {
       $sortas=" sortas=\"$1\"";
    }
  }

  if (exists $Deprecated{$symbol}) {
      $terms .= "<indexterm zone=\"$id\" role=\"deprecated\"><primary$sortas>$symbol</primary></indexterm>";
      $IndexEntriesDeprecated{$symbol}=$id;
      $IndexEntriesFull{$symbol}=$id;
  }
  if (exists $Since{$symbol}) {
     my $since = $Since{$symbol};
     $since =~ s/^\s+//;
     $since =~ s/\s+$//;
     if ($since ne "") {
         $terms .= "<indexterm zone=\"$id\" role=\"$since\"><primary$sortas>$symbol</primary></indexterm>";
     }
     $IndexEntriesSince{$symbol}=$id;
     $IndexEntriesFull{$symbol}=$id;
  }
  if ($terms eq "") {
     $terms .= "<indexterm zone=\"$id\"><primary$sortas>$symbol</primary></indexterm>";
     $IndexEntriesFull{$symbol}=$id;
  }

  return $terms;
 }

#############################################################################
# Function    : MakeDeprecationNote
# Description : This returns a deprecation warning for the given symbol.
# Arguments   : $symbol - the symbol to try to create a warning for.
#############################################################################

sub MakeDeprecationNote {
    my ($symbol) = $_[0];
    my $desc = "";
    if (exists $Deprecated{$symbol}) {
        my $note;

        $desc .= "<warning><para><literal>$symbol</literal> ";

        $note = $Deprecated{$symbol};

        if ($note =~ /^\s*([0-9\.]+)\s*:?/) {
                $desc .= "has been deprecated since version $1 and should not be used in newly-written code.</para>";
        } else {
                $desc .= "is deprecated and should not be used in newly-written code.</para>";
        }
        $note =~ s/^\s*([0-9\.]+)\s*:?\s*//;
        $note =~ s/^\s+//;
        $note =~ s/\s+$//;
        if ($note ne "") {
            $note = &ConvertMarkDown($symbol, $note);
            $desc .= " " . $note;
        }
        $desc .= "</warning>\n";
    }
    return $desc;
}

#############################################################################
# Function    : MakeConditionDescription
# Description : This returns a sumary of conditions for the given symbol.
# Arguments   : $symbol - the symbol to try to create the sumary.
#############################################################################

sub MakeConditionDescription {
    my ($symbol) = $_[0];
    my $desc = "";

    if (exists $Deprecated{$symbol}) {
        if ($desc ne "") {
            $desc .= "|";
        }

        if ($Deprecated{$symbol} =~ /^\s*(.*?)\s*$/) {
                $desc .= "deprecated:$1";
        } else {
                $desc .= "deprecated";
        }
    }

    if (exists $Since{$symbol}) {
        if ($desc ne "") {
            $desc .= "|";
        }

        if ($Since{$symbol} =~ /^\s*(.*?)\s*$/) {
                $desc .= "since:$1";
        } else {
                $desc .= "since";
        }
    }

    if (exists $StabilityLevel{$symbol}) {
        if ($desc ne "") {
            $desc .= "|";
        }
        $desc .= "stability:".$StabilityLevel{$symbol};
    }

    if ($desc ne "") {
        my $cond = $desc;
        $cond =~ s/\"/&quot;/g;
        $desc=" condition=\"".$cond."\"";
        #("condition for '$symbol' = '$desc'\n");
    }
    return $desc;
}

#############################################################################
# Function    : GetHierarchy
# Description : Returns the DocBook output describing the ancestors and
#               immediate children of a GObject subclass. It uses the
#               global @Objects and @ObjectLevels arrays to walk the tree.
#
# Arguments   : $object - the GtkObject subclass.
#               @hierarchy - previous hierarchy
#############################################################################

sub GetHierarchy {
    my ($object,$hierarchy_ref) = @_;
    my @hierarchy = @{$hierarchy_ref};
    
    # Find object in the objects array.
    my $found = 0;
    my @children = ();
    my $i;
    my $level;
    my $j;
    for ($i = 0; $i < @Objects; $i++) {
        if ($found) {
            if ($ObjectLevels[$i] <= $level) {
            last;
        }
            elsif ($ObjectLevels[$i] == $level + 1) {
                push (@children, $Objects[$i]);
            }
        }
        elsif ($Objects[$i] eq $object) {
            $found = 1;
            $j = $i;
            $level = $ObjectLevels[$i];
        }
    }
    if (!$found) {
        return @hierarchy;
    }

    # Walk up the hierarchy, pushing ancestors onto the ancestors array.
    my @ancestors = ();
    push (@ancestors, $object);
    #("Level: $level\n");
    while ($level > 1) {
        $j--;
        if ($ObjectLevels[$j] < $level) {
            push (@ancestors, $Objects[$j]);
            $level = $ObjectLevels[$j];
            #("Level: $level\n");
        }
    }

    # Output the ancestors, indented and with links.
    my $last_index = 0;
    $level = 1;
    for ($i = $#ancestors; $i >= 0; $i--) {
        my $entry_text;
        my $alt_text;
        my $ancestor = $ancestors[$i];
        my $ancestor_id = &CreateValidSGMLID ($ancestor);
        my $indent = ' ' x ($level * 4);
        # Don't add a link to the current object, i.e. when i == 0.
        if ($i > 0) {
            $entry_text = $indent . "<link linkend=\"$ancestor_id\">$ancestor</link>";
            $alt_text = $indent . $ancestor;
        } else {
            $entry_text = $indent . $ancestor;
            $alt_text = $indent . "<link linkend=\"$ancestor_id\">$ancestor</link>";
        }
        #("Checking for '$entry_text' or '$alt_text'"); 
        # Check if we already have this object
        my $index = -1;
        for ($j = 0; $j <= $#hierarchy; $j++) {
            if (($hierarchy[$j] eq $entry_text) or ($hierarchy[$j] eq $alt_text)) {
                $index = $j;
                last;
            }
        }
        if ($index == -1) {
            # We have a new entry, find insert position in alphabetical order
            my $found = 0;
            for ($j = $last_index; $j <= $#hierarchy; $j++) {
                if ($hierarchy[$j] !~ m/^${indent}/) {
                    $last_index = $j;
                    $found = 1;
                    last;
                } elsif ($hierarchy[$j] =~ m/^${indent}[^ ]/) {
                    my $stripped_text = $hierarchy[$j];
                    if ($entry_text !~ m/<link linkend/) {
                        $stripped_text =~ s%<link linkend="[A-Za-z]*">%%;
                        $stripped_text =~ s%</link>%%;
                    }
                    if ($entry_text lt $stripped_text) {
                        $last_index = $j;
                        $found = 1;
                        last;
                    } 
                }
            }
            # Append to bottom
            if (!$found) {
              $last_index = 1 + $#hierarchy;
            }
            splice @hierarchy, $last_index, 0, ($entry_text);
            $last_index++;
        } else {
            # Already have this one, make sure we use the not linked version
            if ($entry_text !~ m/<link linkend=/) {
              $hierarchy[$j] = $entry_text;
            }
            # Remember index as base insert point
            $last_index = $index + 1;
        }
        $level++;
    }
    # Output the children, indented and with links.
    for ($i = 0; $i <= $#children; $i++) {
        my $id = &CreateValidSGMLID ($children[$i]);
        my $indented_text = ' ' x ($level * 4) . "<link linkend=\"$id\">$children[$i]</link>";
        splice @hierarchy, $last_index, 0, ($indented_text);
        $last_index++;
    }    

    return @hierarchy; 
}

#############################################################################
# Function    : GetInterfaces
# Description : Returns the DocBook output describing the interfaces
#               implemented by a class. It uses the global %Interfaces hash.
# Arguments   : $object - the GtkObject subclass.
#############################################################################

sub GetInterfaces {
    my ($object) = @_;
    my $text = "";
    my $i;

    # Find object in the objects array.
    if (exists($Interfaces{$object})) {
        my @ifaces = split(' ', $Interfaces{$object});
        $text = <<EOF;
<para>
$object implements
EOF
        for ($i = 0; $i <= $#ifaces; $i++) {
            my $id = &CreateValidSGMLID ($ifaces[$i]);
            $text .= " <link linkend=\"$id\">$ifaces[$i]</link>";
            if ($i < $#ifaces - 1) {
                $text .= ', ';
            }
            elsif ($i < $#ifaces) {
                $text .= ' and ';
            }
            else {
                $text .= '.';
            }
        }
        $text .= <<EOF;
</para>
EOF
    }

    return $text;
}

#############################################################################
# Function    : GetImplementations
# Description : Returns the DocBook output describing the implementations
#               of an interface. It uses the global %Interfaces hash.
# Arguments   : $object - the GtkObject subclass.
#############################################################################

sub GetImplementations {
    my ($object) = @_;
    my @impls = ();
    my $text = "";
    my $i;
    foreach my $key (keys %Interfaces) {
        if ($Interfaces{$key} =~ /\b$object\b/) {
            push (@impls, $key);
        }
    }
    if ($#impls >= 0) {
        @impls = sort @impls;
        $text = <<EOF;
<para>
$object is implemented by
EOF
        for ($i = 0; $i <= $#impls; $i++) {
            my $id = &CreateValidSGMLID ($impls[$i]);
            $text .= " <link linkend=\"$id\">$impls[$i]</link>";
            if ($i < $#impls - 1) {
                $text .= ', ';
            }
            elsif ($i < $#impls) {
                $text .= ' and ';
            }
            else {
                $text .= '.';
            }
        }
        $text .= <<EOF;
</para>
EOF
    }
    return $text;
}


#############################################################################
# Function    : GetPrerequisites
# Description : Returns the DocBook output describing the prerequisites
#               of an interface. It uses the global %Prerequisites hash.
# Arguments   : $iface - the interface.
#############################################################################

sub GetPrerequisites {
    my ($iface) = @_;
    my $text = "";
    my $i;

    if (exists($Prerequisites{$iface})) {
        $text = <<EOF;
<para>
$iface requires
EOF
        my @prereqs = split(' ', $Prerequisites{$iface});
        for ($i = 0; $i <= $#prereqs; $i++) {
            my $id = &CreateValidSGMLID ($prereqs[$i]);
            $text .= " <link linkend=\"$id\">$prereqs[$i]</link>";
            if ($i < $#prereqs - 1) {
                $text .= ', ';
            }
            elsif ($i < $#prereqs) {
                $text .= ' and ';
            }
            else {
                $text .= '.';
            }
        }
        $text .= <<EOF;
</para>
EOF
    }
    return $text;
}

#############################################################################
# Function    : GetDerived
# Description : Returns the DocBook output describing the derived interfaces
#               of an interface. It uses the global %Prerequisites hash.
# Arguments   : $iface - the interface.
#############################################################################

sub GetDerived {
    my ($iface) = @_;
    my $text = "";
    my $i;

    my @derived = ();
    foreach my $key (keys %Prerequisites) {
        if ($Prerequisites{$key} =~ /\b$iface\b/) {
            push (@derived, $key);
        }
    }
    if ($#derived >= 0) {
        @derived = sort @derived;
        $text = <<EOF;
<para>
$iface is required by
EOF
        for ($i = 0; $i <= $#derived; $i++) {
            my $id = &CreateValidSGMLID ($derived[$i]);
            $text .= " <link linkend=\"$id\">$derived[$i]</link>";
            if ($i < $#derived - 1) {
                $text .= ', ';
            }
            elsif ($i < $#derived) {
                $text .= ' and ';
            }
            else {
                $text .= '.';
            }
        }
        $text .= <<EOF;
</para>
EOF
    }
    return $text;
}


#############################################################################
# Function    : GetSignals
# Description : Returns the synopsis and detailed description DocBook output
#                for the signal handlers of a given GtkObject subclass.
# Arguments   : $object - the GtkObject subclass, e.g. 'GtkButton'.
#############################################################################

sub GetSignals {
    my ($object) = @_;
    my $synop = "";
    my $desc = "";

    my $i;
    for ($i = 0; $i <= $#SignalObjects; $i++) {
        if ($SignalObjects[$i] eq $object) {
            #("Found signal: $SignalNames[$i]\n");
            my $name = $SignalNames[$i];
            my $symbol = "${object}::${name}";
            my $id = &CreateValidSGMLID ("$object-$name");

            $desc .= "<refsect2 id=\"$id\" role=\"signal\"><title>The <literal>“$name”</literal> signal</title>\n";
            $desc .= MakeIndexterms($symbol, $id);
            $desc .= "\n";
            $desc .= OutputSymbolExtraLinks($symbol);

            $desc .= "<programlisting language=\"C\">";

            $SignalReturns[$i] =~ m/\s*(const\s+)?(\w+)\s*(\**)/;
            my $type_modifier = defined($1) ? $1 : "";
            my $type = $2;
            my $pointer = $3;
            my $xref = &MakeXRef ($type, &tagify($type, "returnvalue"));

            my $ret_type_output = "$type_modifier$xref$pointer";
            my $callback_name = "user_function";
            $desc  .= "${ret_type_output}\n${callback_name} (";

            my $indentation = ' ' x (length($callback_name) + 2);
            my $pad = $indentation;

            my $sourceparams = $SourceSymbolParams{$symbol};
            my @params = split ("\n", $SignalPrototypes[$i]);
            my $j;
            my $l;
            my $type_len = length("gpointer");
            my $name_len = length("user_data");
            # do two passes, the first one is to calculate padding
            for ($l = 0; $l < 2; $l++) {
                for ($j = 0; $j <= $#params; $j++) {
                    my $param_name;
                    # allow alphanumerics, '_', '[' & ']' in param names
                    if ($params[$j] =~ m/^\s*(\w+)\s*(\**)\s*([\w\[\]]+)\s*$/) {
                        $type = $1;
                        $pointer = $2;
                        if (defined($sourceparams)) {
                            $param_name = $$sourceparams[$PARAM_FIELD_COUNT * $j];
                        }
                        else {
                            $param_name = $3;
                        }
                        if (!defined($param_name)) {
                            $param_name = "arg$j";
                        }
                        if ($l == 0) {
                            if (length($type) + length($pointer) > $type_len) {
                                $type_len = length($type) + length($pointer);
                            }
                            if (length($param_name) > $name_len) {
                                $name_len = length($param_name);
                            }
                        }
                        else {
                            $xref = &MakeXRef ($type, &tagify($type, "type"));
                            $pad = ' ' x ($type_len - length($type) - length($pointer));
                            $desc .= "$xref$pad $pointer${param_name},\n";
                            $desc .= $indentation;
                        }
                    } else {
                        &LogWarning (&GetSymbolSourceFile ($symbol), &GetSymbolSourceLine($symbol),
                             "Can't parse arg: $params[$j]\nArgs:$SignalPrototypes[$i]");
                    }
                }
            }
            $xref = &MakeXRef ("gpointer", &tagify("gpointer", "type"));
            $pad = ' ' x ($type_len - length("gpointer"));
            $desc  .= "$xref$pad user_data)";
            $desc  .= "</programlisting>\n";

            my $flags = $SignalFlags[$i];
            my $flags_string = "";

            if (defined ($flags)) {
              if ($flags =~ m/f/) {
                $flags_string = "<link linkend=\"G-SIGNAL-RUN-FIRST:CAPS\">Run First</link>";
              }
              elsif ($flags =~ m/l/) {
                $flags_string = "<link linkend=\"G-SIGNAL-RUN-LAST:CAPS\">Run Last</link>";
              }
              elsif ($flags =~ m/c/) {
                $flags_string = "<link linkend=\"G-SIGNAL-RUN-CLEANUP:CAPS\">Cleanup</link>";
                $flags_string = "Cleanup";
              }
              if ($flags =~ m/r/) {
                if ($flags_string) { $flags_string .= " / "; }
                $flags_string = "<link linkend=\"G-SIGNAL-NO-RECURSE:CAPS\">No Recursion</link>";
              }
              if ($flags =~ m/d/) {
                if ($flags_string) { $flags_string .= " / "; }
                $flags_string = "<link linkend=\"G-SIGNAL-DETAILED:CAPS\">Has Details</link>";
              }
              if ($flags =~ m/a/) {
                if ($flags_string) { $flags_string .= " / "; }
                $flags_string = "<link linkend=\"G-SIGNAL-ACTION:CAPS\">Action</link>";
              }
              if ($flags =~ m/h/) {
                if ($flags_string) { $flags_string .= " / "; }
                $flags_string = "<link linkend=\"G-SIGNAL-NO-HOOKS:CAPS\">No Hooks</link>";
              }
            }

            $synop .= "<row><entry role=\"signal_type\">${ret_type_output}</entry><entry role=\"signal_name\"><link linkend=\"$id\">${name}</link></entry><entry role=\"signal_flags\">${flags_string}</entry></row>\n";

            my $parameters = &OutputParamDescriptions ("SIGNAL", $symbol);

            $AllSymbols{$symbol} = 1;
            if (defined ($SymbolDocs{$symbol})) {
                my $symbol_docs = &ConvertMarkDown($symbol, $SymbolDocs{$symbol});

                $desc .= $symbol_docs;

                if (!IsEmptyDoc($SymbolDocs{$symbol})) {
                    $AllDocumentedSymbols{$symbol} = 1;
                }
            }
            if (defined ($SymbolAnnotations{$symbol})) {
                my $param_desc = $SymbolAnnotations{$symbol};
                my $param_annotations = "";
                ($param_desc,$param_annotations) = &ExpandAnnotation($symbol, $param_desc);
                if ($param_annotations ne "") {
                    $desc .= "\n<para>$param_annotations</para>";
                }
            }
            $desc .= &MakeDeprecationNote($symbol);

            $desc .= $parameters;
            if ($flags_string) {
                $desc  .= "<para>Flags: $flags_string</para>\n";
            }
            $desc .= OutputSymbolTraits ($symbol);
            $desc .= "</refsect2>";
        }
    }
    return ($synop, $desc);
}


#############################################################################
# Function    : GetArgs
# Description : Returns the synopsis and detailed description DocBook output
#                for the Args of a given GtkObject subclass.
# Arguments   : $object - the GtkObject subclass, e.g. 'GtkButton'.
#############################################################################

sub GetArgs {
    my ($object) = @_;
    my $synop = "";
    my $desc = "";
    my $child_synop = "";
    my $child_desc = "";
    my $style_synop = "";
    my $style_desc = "";

    my $i;
    for ($i = 0; $i <= $#ArgObjects; $i++) {
        if ($ArgObjects[$i] eq $object) {
            #("Found arg: $ArgNames[$i]\n");
            my $name = $ArgNames[$i];
            my $flags = $ArgFlags[$i];
            my $flags_string = "";
            my $kind = "";
            my $id_sep = "";

            if ($flags =~ m/c/) {
                $kind = "child property";
                $id_sep = "c-";
            }
            elsif ($flags =~ m/s/) {
                $kind = "style property";
                $id_sep = "s-";
            }
            else {
                $kind = "property";
            }

            # Remember only one colon so we don't clash with signals.
            my $symbol = "${object}:${name}";
            # use two dashes and ev. an extra separator here for the same reason.
            my $id = &CreateValidSGMLID ("$object--$id_sep$name");

            my $type = $ArgTypes[$i];
            my $type_output;
            my $range = $ArgRanges[$i];
            my $range_output = CreateValidSGML ($range);
            my $default = $ArgDefaults[$i];
            my $default_output = CreateValidSGML ($default);

            if ($type eq "GtkString") {
                $type = "char&#160;*";
            }
            if ($type eq "GtkSignal") {
                $type = "GtkSignalFunc, gpointer";
                $type_output = &MakeXRef ("GtkSignalFunc") . ", "
                    . &MakeXRef ("gpointer");
            } elsif ($type =~ m/^(\w+)\*$/) {
                $type_output = &MakeXRef ($1, &tagify($1, "type")) . "&#160;*";
            } else {
                $type_output = &MakeXRef ($type, &tagify($type, "type"));
            }

            if ($flags =~ m/r/) {
                $flags_string = "Read";
            }
            if ($flags =~ m/w/) {
                if ($flags_string) { $flags_string .= " / "; }
                $flags_string .= "Write";
            }
            if ($flags =~ m/x/) {
                if ($flags_string) { $flags_string .= " / "; }
                $flags_string .= "Construct";
            }
            if ($flags =~ m/X/) {
                if ($flags_string) { $flags_string .= " / "; }
                $flags_string .= "Construct Only";
            }

            $AllSymbols{$symbol} = 1;
            my $blurb = "";
            if (defined($SymbolDocs{$symbol}) &&
                !IsEmptyDoc($SymbolDocs{$symbol})) {
                $blurb = &ConvertMarkDown($symbol, $SymbolDocs{$symbol});
                #(".. [$SymbolDocs{$symbol}][$blurb]\n");
                $AllDocumentedSymbols{$symbol} = 1;
            }
            else {
                if ($ArgBlurbs[$i] ne "") {
                    $blurb = "<para>" . &CreateValidSGML ($ArgBlurbs[$i]) . "</para>";
                    $AllDocumentedSymbols{$symbol} = 1;
                } else {
                    # FIXME: print a warning?
                    #(".. no description\n");
                }
            }

            my $pad1 = " " x (24 - length ($name));

            my $arg_synop = "<row><entry role=\"property_type\">$type_output</entry><entry role=\"property_name\"><link linkend=\"$id\">$name</link></entry><entry role=\"property_flags\">$flags_string</entry></row>\n";
            my $arg_desc = "<refsect2 id=\"$id\" role=\"property\"><title>The <literal>“$name”</literal> $kind</title>\n";
            $arg_desc .= MakeIndexterms($symbol, $id);
            $arg_desc .= "\n";
            $arg_desc .= OutputSymbolExtraLinks($symbol);

            $arg_desc .= "<programlisting>  “$name”$pad1 $type_output</programlisting>\n";
            $arg_desc .= $blurb;
            if (defined ($SymbolAnnotations{$symbol})) {
                my $param_desc = $SymbolAnnotations{$symbol};
                my $param_annotations = "";
                ($param_desc,$param_annotations) = &ExpandAnnotation($symbol, $param_desc);
                if ($param_annotations ne "") {
                    $arg_desc .= "\n<para>$param_annotations</para>";
                }
            }
            $arg_desc .= &MakeDeprecationNote($symbol);

            if ($flags_string) {
              $arg_desc  .= "<para>Flags: $flags_string</para>\n";
            }
            if ($range ne "") {
                $arg_desc .= "<para>Allowed values: $range_output</para>\n";
            }
            if ($default ne "") {
                $arg_desc .= "<para>Default value: $default_output</para>\n";
            }
            $arg_desc .= OutputSymbolTraits ($symbol);
            $arg_desc .= "</refsect2>\n";

            if ($flags =~ m/c/) {
                $child_synop .= $arg_synop;
                $child_desc .= $arg_desc;
            }
            elsif ($flags =~ m/s/) {
                $style_synop .= $arg_synop;
                $style_desc .= $arg_desc;
            }
            else {
                $synop .= $arg_synop;
                $desc .= $arg_desc;
            }
        }
    }
    return ($synop, $child_synop, $style_synop, $desc, $child_desc, $style_desc);
}


#############################################################################
# Function    : ReadSourceDocumentation
# Description : This reads in the documentation embedded in comment blocks
#                in the source code (for Gnome).
#
#                Parameter descriptions override any in the template files.
#                Function descriptions are placed before any description from
#                the template files.
#
#                It recursively descends the source directory looking for .c
#                files and scans them looking for specially-formatted comment
#                blocks.
#
# Arguments   : $source_dir - the directory to scan.
#############m###############################################################

sub ReadSourceDocumentation {
    my ($source_dir) = @_;
    my ($file, $dir, @suffix_list, $suffix);

    # prepend entries from @SOURCE_DIR
    for my $dir (@SOURCE_DIRS) {
        # Check if the filename is in the ignore list.
        if ($source_dir =~ m%^\Q$dir\E/(.*)$% and $IGNORE_FILES =~ m/(\s|^)\Q$1\E(\s|$)/) {
            #("Skipping source directory: $source_dir");
            return;
        } else {
            #("No match for: ".($1 || $source_dir));
        }
    }

    #("Scanning source directory: $source_dir");

    # This array holds any subdirectories found.
    my (@subdirs) = ();

    @suffix_list = split (/,/, $SOURCE_SUFFIXES);

    opendir (SRCDIR, $source_dir)
        || die "Can't open source directory $source_dir: $!";

    foreach $file (readdir (SRCDIR)) {
      if ($file =~ /^\./) {
        next;
      } elsif (-d "$source_dir/$file") {
        push (@subdirs, $file);
      } elsif (@suffix_list) {
        foreach $suffix (@suffix_list) {
          if ($file =~ m/\.\Q${suffix}\E$/) {
            &ScanSourceFile ("$source_dir/$file");
          }
        }
      } elsif ($file =~ m/\.[ch]$/) {
        &ScanSourceFile ("$source_dir/$file");
      }
    }
    closedir (SRCDIR);

    # Now recursively scan the subdirectories.
    foreach $dir (@subdirs) {
        &ReadSourceDocumentation ("$source_dir/$dir");
    }
}


#############################################################################
# Function    : ScanSourceFile
# Description : Scans one source file looking for specially-formatted comment
#                blocks. Later &MergeSourceDocumentation is used to merge any
#                documentation found with the documentation already read in
#                from the template files.
#
# Arguments   : $file - the file to scan.
#############################################################################

sub ScanSourceFile {
    my ($file) = @_;
    my $basename;

    # prepend entries from @SOURCE_DIR
    for my $dir (@SOURCE_DIRS) {
        # Check if the filename is in the ignore list.
        if ($file =~ m%^\Q$dir\E/(.*)$% and $IGNORE_FILES =~ m/(\s|^)\Q$1\E(\s|$)/) {
            #("Skipping source file: $file");
            return;
        }
    }

    if ($file =~ m/^.*[\/\\]([^\/\\]*)$/) {
        $basename = $1;
    } else {
        &LogWarning ($file, 1, "Can't find basename for this filename.");
        $basename = $file;
    }

    # Check if the basename is in the list of files to ignore.
    if ($IGNORE_FILES =~ m/(\s|^)\Q${basename}\E(\s|$)/) {
        #("Skipping source file: $file");
        return;
    }

    #("Scanning source file: $file");

    open (SRCFILE, $file)
        || die "Can't open $file: $!";
    my $in_comment_block = 0;
    my $symbol;
    my $in_part = "";
    my ($description, $return_desc);
    my ($since_desc, $stability_desc, $deprecated_desc);
    my $current_param;
    my @params;
    while (<SRCFILE>) {
        # Look for the start of a comment block.
        if (!$in_comment_block) {
            if (m%^\s*/\*.*\*/%) {
                #one-line comment - not gtkdoc
            } elsif (m%^\s*/\*\*\s%) {
                #("Found comment block start\n");

                $in_comment_block = 1;

                # Reset all the symbol data.
                $symbol = "";
                $in_part = "";
                $description = "";
                $return_desc = "";
                $since_desc = "";
                $deprecated_desc = "";
                $stability_desc = "";
                $current_param = -1;
                @params = ();
            }
            next;
        }

        # We're in a comment block. Check if we've found the end of it.
        if (m%^\s*\*+/%) {
            if (!$symbol) {
                # maybe its not even meant to be a gtk-doc comment?
                &LogWarning ($file, $., "Symbol name not found at the start of the comment block.");
            } else {
                # Add the return value description onto the end of the params.
                if ($return_desc) {
                    # TODO(ensonic): check for duplicated Return docs
                    # &LogWarning ($file, $., "Multiple Returns for $symbol.");
                    push (@params, "Returns");
                    push (@params, $return_desc);
                }
                # Convert special characters
                $description = &ConvertSGMLChars ($symbol, $description);
                my $k;
                for ($k = 1; $k <= $#params; $k += $PARAM_FIELD_COUNT) {
                    $params[$k] = &ConvertSGMLChars ($symbol, $params[$k]);
                }

                # Handle Section docs
                if ($symbol =~ m/SECTION:\s*(.*)/) {
                    my $real_symbol=$1;
                    my $key;

                    if (scalar %KnownSymbols) {
                        if ((! defined($KnownSymbols{"$TMPL_DIR/$real_symbol:Long_Description"})) || $KnownSymbols{"$TMPL_DIR/$real_symbol:Long_Description"} != 1) {
                            &LogWarning ($file, $., "Section $real_symbol is not defined in the $MODULE-sections.txt file.");
                        }
                    }

                    #("SECTION DOCS found in source for : '$real_symbol'\n");
                    for ($k = 0; $k <= $#params; $k += $PARAM_FIELD_COUNT) {
                        #("   '".$params[$k]."'\n");
                        $params[$k] = "\L$params[$k]";
                        undef $key;
                        if ($params[$k] eq "short_description") {
                            $key = "$TMPL_DIR/$real_symbol:Short_Description";
                        } elsif ($params[$k] eq "see_also") {
                            $key = "$TMPL_DIR/$real_symbol:See_Also";
                        } elsif ($params[$k] eq "title") {
                            $key = "$TMPL_DIR/$real_symbol:Title";
                        } elsif ($params[$k] eq "stability") {
                            $key = "$TMPL_DIR/$real_symbol:Stability_Level";
                        } elsif ($params[$k] eq "section_id") {
                            $key = "$TMPL_DIR/$real_symbol:Section_Id";
                        } elsif ($params[$k] eq "include") {
                            $key = "$TMPL_DIR/$real_symbol:Include";
                        } elsif ($params[$k] eq "image") {
                            $key = "$TMPL_DIR/$real_symbol:Image";
                        }
                        if (defined($key)) {
                            $SourceSymbolDocs{$key}=$params[$k+1];
                            $SourceSymbolSourceFile{$key} = $file;
                            $SourceSymbolSourceLine{$key} = $.;
                        }
                    }
                    $SourceSymbolDocs{"$TMPL_DIR/$real_symbol:Long_Description"}=$description;
                    $SourceSymbolSourceFile{"$TMPL_DIR/$real_symbol:Long_Description"} = $file;
                    $SourceSymbolSourceLine{"$TMPL_DIR/$real_symbol:Long_Description"} = $.;
                    #$SourceSymbolTypes{$symbol} = "SECTION";
                } else {
                    #("SYMBOL DOCS found in source for : '$symbol' ",length($description), "\n");
                    $SourceSymbolDocs{$symbol} = $description;
                    $SourceSymbolParams{$symbol} = [ @params ];
                    # FIXME $SourceSymbolTypes{$symbol} = "STRUCT,SIGNAL,ARG,FUNCTION,MACRO";
                    #if (defined $DeclarationTypes{$symbol}) {
                    #    $SourceSymbolTypes{$symbol} = $DeclarationTypes{$symbol}
                    #}
                    $SourceSymbolSourceFile{$symbol} = $file;
                    $SourceSymbolSourceLine{$symbol} = $.;
                }                

                if ($since_desc) {
                     ($since_desc, my @extra_lines) = split ("\n", $since_desc);
                     $since_desc =~ s/^\s+//;
                     $since_desc =~ s/\s+$//;
                     #("Since($symbol) : [$since_desc]\n");
                     $Since{$symbol} = &ConvertSGMLChars ($symbol, $since_desc);
                     if(scalar @extra_lines) {
                         &LogWarning ($file, $., "multi-line since docs found");
                     }
                }

                if ($stability_desc) {
                    $stability_desc = &ParseStabilityLevel($stability_desc, $file, $., "Stability level for $symbol");
                    $StabilityLevel{$symbol} = &ConvertSGMLChars ($symbol, $stability_desc);
                }

                if ($deprecated_desc) {
                    if (!exists $Deprecated{$symbol}) {
                         # don't warn for signals and properties
                         #if ($symbol !~ m/::?(.*)/) {
                         if (defined $DeclarationTypes{$symbol}) {
                             &LogWarning ($file, $.,
                                 "$symbol is deprecated in the inline comments, but no deprecation guards were found around the declaration.".
                                 " (See the --deprecated-guards option for gtkdoc-scan.)");
                         }
                    }
                    $Deprecated{$symbol} = &ConvertSGMLChars ($symbol, $deprecated_desc);
                }
            }

            $in_comment_block = 0;
            next;
        }

        # Get rid of ' * ' at start of every line in the comment block.
        s%^\s*\*\s?%%;
        # But make sure we don't get rid of the newline at the end.
        if (!$_) {
            $_ = "\n";
        }
        #("scanning :$_");

        # If we haven't found the symbol name yet, look for it.
        if (!$symbol) {
            if (m%^\s*(SECTION:\s*\S+)%) {
                $symbol = $1;
                #("SECTION DOCS found in source for : '$symbol'\n");
            } elsif (m%^\s*([\w:-]*\w)\s*:?\s*(\([-A-Za-z0-9._() ]+?\)\s*)*$%) {
                $symbol = $1;
                my $annotation = $2;
                #("SYMBOL DOCS found in source for : '$symbol'\n");
                if (defined($annotation)) {
                    chomp($annotation);
                    if ($annotation ne "") {
                        $SymbolAnnotations{$symbol} = $annotation;
                        #("remaining text for $symbol: '$annotation'\n");
                    }
                }
            }
            next;
        }

        if ($in_part eq "description") {
            # Get rid of 'Description:'
            s%^\s*Description:%%;
        }

        if (m%^\s*(returns|return\s+value):%i) {
            # we're in param section and have not seen the blank line
            if($in_part ne "") {
              $return_desc = $';
              $in_part = "return";
              next;
            }
        } elsif (m%^\s*since:%i) {
            # we're in param section and have not seen the blank line
            if($in_part ne "param") {
              $since_desc = $';
              $in_part = "since";
              next;
            }
        } elsif (m%^\s*deprecated:%i) {
            # we're in param section and have not seen the blank line
            if($in_part ne "param") {
              $deprecated_desc = $';
              $in_part = "deprecated";
              next;
            }
        } elsif (m%^\s*stability:%i) {
            $stability_desc = $';
            $in_part = "stability";
            next;
        }

        if ($in_part eq "description") {
            $description .= $_;
            next;
        } elsif ($in_part eq "return") {
            $return_desc .= $_;
            next;
        } elsif ($in_part eq "since") {
            $since_desc .= $_;
            next;
        } elsif ($in_part eq "stability") {
            $stability_desc .= $_;
            next;
        } elsif ($in_part eq "deprecated") {
            $deprecated_desc .= $_;
            next;
        }

        # We must be in the parameters. Check for the empty line below them.
        if (m%^\s*$%) {
            $in_part = "description";
            next;
        }

        # Look for a parameter name.
        if (m%^\s*@(\S+)\s*:\s*%) {
            my $param_name = $1;
            my $param_desc = $';

            #("Found parameter: $param_name\n");
            # Allow varargs variations
            if ($param_name =~ m/^\.\.\.$/) {
                $param_name = "...";
            }
            #("Found param for symbol $symbol : '$param_name'= '$_'");

            push (@params, $param_name);
            push (@params, $param_desc);
            $current_param += $PARAM_FIELD_COUNT;
            $in_part = "param";
            next;
        } elsif ($in_part eq "") {
            #("continuation for $symbol annotation '$_'");
            my $annotation = $_;
            $annotation =~ s/^\s+|\s+$//g ; 
            $SymbolAnnotations{$symbol} .= $annotation;
            next;
        }

        # We must be in the middle of a parameter description, so add it on
        # to the last element in @params.
        if ($current_param == -1) {
            &LogWarning ($file, $., "Parsing comment block file : parameter expected, but got '$_'");
        } else {
            $params[$#params] .= $_;
        }
    }
    close (SRCFILE);
}

#############################################################################
# Function    : OutputMissingDocumentation
# Description : Outputs report of documentation coverage to a file
#
# Arguments   : none
#############################################################################

sub OutputMissingDocumentation {
    my $old_undocumented_file = "$ROOT_DIR/$MODULE-undocumented.txt";
    my $new_undocumented_file = "$ROOT_DIR/$MODULE-undocumented.new";

    my $n_documented = 0;
    my $n_incomplete = 0;
    my $total = 0;
    my $symbol;
    my $percent;
    my $msg;
    my $buffer = "";
    my $buffer_deprecated = "";
    my $buffer_descriptions = "";

    open(UNDOCUMENTED, ">$new_undocumented_file")
      || die "Can't create $new_undocumented_file";

    foreach $symbol (sort (keys (%AllSymbols))) {
        # FIXME: should we print LogWarnings for undocumented stuff?
        # DEBUG
        #my $ssfile = &GetSymbolSourceFile($symbol);
        #my $ssline = &GetSymbolSourceLine($symbol);
        #my $location = "defined at " . (defined($ssfile)?$ssfile:"?") . ":" . (defined($ssline)?$ssline:"0") . "\n";
        # DEBUG
        if ($symbol !~ /:(Title|Long_Description|Short_Description|See_Also|Stability_Level|Include|Section_Id|Image)/) {
            $total++;
            if (exists ($AllDocumentedSymbols{$symbol})) {
                $n_documented++;
                if (exists ($AllIncompleteSymbols{$symbol})) {
                    $n_incomplete++;
                    $buffer .= $symbol . " (" . $AllIncompleteSymbols{$symbol} . ")\n";
                    #$buffer .= "\t0: ".$location;
                }
            } elsif (exists $Deprecated{$symbol}) {
                if (exists ($AllIncompleteSymbols{$symbol})) {
                    $n_incomplete++;
                    $buffer_deprecated .= $symbol . " (" . $AllIncompleteSymbols{$symbol} . ")\n";
                    #$buffer .= "\t1a: ".$location;
                } else {
                    $buffer_deprecated .= $symbol . "\n";
                    #$buffer .= "\t1b: ".$location;
                }
            } else {
                if (exists ($AllIncompleteSymbols{$symbol})) {
                    $n_incomplete++;
                    $buffer .= $symbol . " (" . $AllIncompleteSymbols{$symbol} . ")\n";
                    #$buffer .= "\t2a: ".$location;
                } else {
                    $buffer .= $symbol . "\n";
                    #$buffer .= "\t2b: ".$location;
                }
            }
        } elsif ($symbol =~ /:(Long_Description|Short_Description)/) {
            $total++;
            if (((exists ($SymbolDocs{$symbol})) && (length ($SymbolDocs{$symbol}) > 0))
            || ((exists ($AllDocumentedSymbols{$symbol})) && (length ($AllDocumentedSymbols{$symbol}) > 0))) {
              $n_documented++;
            } else {
              # cut off the leading namespace ($TMPL_DIR)
              $symbol =~ m/^.*\/(.*)$/;
              $buffer_descriptions .= $1 . "\n";
            }
        }
    }

    if ($total == 0) {
      $percent = 100;
    } else {
      $percent = ($n_documented / $total) * 100.0;
    }

    printf UNDOCUMENTED "%.0f%% symbol docs coverage.\n", $percent;
    print UNDOCUMENTED "$n_documented symbols documented.\n";
    print UNDOCUMENTED "$n_incomplete symbols incomplete.\n";
    print UNDOCUMENTED ($total - $n_documented) . " not documented.\n";

    if ($buffer_deprecated ne "") {
      $buffer .= "\n" . $buffer_deprecated;
    }
    if ($buffer_descriptions ne "") {
      $buffer .= "\n" . $buffer_descriptions;
    }
    if ($buffer ne "") {
      print UNDOCUMENTED "\n\n$buffer";
    }
    close (UNDOCUMENTED);

    return &UpdateFileIfChanged ($old_undocumented_file, $new_undocumented_file, 0);

    printf "%.0f%% symbol docs coverage", $percent;
    print "($n_documented symbols documented, $n_incomplete symbols incomplete, " . ($total - $n_documented) . " not documented)\n";
    print "See $MODULE-undocumented.txt for a list of missing docs.\nThe doc coverage percentage doesn't include intro sections.\n";
}


#############################################################################
# Function    : OutputUndeclaredSymbols
# Description : Outputs symbols that are listed in the section file, but not
#               declaration is found in the sources
#
# Arguments   : none
#############################################################################

sub OutputUndeclaredSymbols {
    my $old_undeclared_file = "$ROOT_DIR/$MODULE-undeclared.txt";
    my $new_undeclared_file = "$ROOT_DIR/$MODULE-undeclared.new";

    open(UNDECLARED, ">$new_undeclared_file")
        || die "Can't create $new_undeclared_file";

    if (%UndeclaredSymbols) {
        print UNDECLARED (join("\n", sort keys %UndeclaredSymbols));
        print UNDECLARED "\n";
        print "See $MODULE-undeclared.txt for the list of undeclared symbols.\n"
    }
    close(UNDECLARED);

    return &UpdateFileIfChanged ($old_undeclared_file, $new_undeclared_file, 0);
}

#############################################################################
# Function    : OutputUnusedSymbols
# Description : Outputs symbols that are documented in comments, but not
#               declared in the sources
#
# Arguments   : none
#############################################################################

sub OutputUnusedSymbols {
    my $num_unused = 0;
    my $old_unused_file = "$ROOT_DIR/$MODULE-unused.txt";
    my $new_unused_file = "$ROOT_DIR/$MODULE-unused.new";

    open (UNUSED, ">$new_unused_file")
        || die "Can't open $new_unused_file";
    my ($symbol);
    foreach $symbol (sort keys (%Declarations)) {
        if (!defined ($DeclarationOutput{$symbol})) {
            print (UNUSED "$symbol\n");
            $num_unused++;
        }
    }
    foreach $symbol (sort (keys (%AllUnusedSymbols))) {
        print (UNUSED "$symbol(" . $AllUnusedSymbols{$symbol} . ")\n");
        $num_unused++;
    }
    close (UNUSED);
    if ($num_unused != 0) {
        &LogWarning ($old_unused_file, 1, "$num_unused unused declarations.".
            "They should be added to $MODULE-sections.txt in the appropriate place.");
    }

    return &UpdateFileIfChanged ($old_unused_file, $new_unused_file, 0);
}


#############################################################################
# Function    : OutputAllSymbols
# Description : Outputs list of all symbols to a file
#
# Arguments   : none
#############################################################################

sub OutputAllSymbols {
     my $n_documented = 0;
     my $total = 0;
     my $symbol;
     my $percent;
     my $msg;

     open (SYMBOLS, ">$ROOT_DIR/$MODULE-symbols.txt")
          || die "Can't create $ROOT_DIR/$MODULE-symbols.txt: $!";

     foreach $symbol (sort (keys (%AllSymbols))) {
          print SYMBOLS $symbol . "\n";
     }

     close (SYMBOLS);
}

#############################################################################
# Function    : OutputSymbolsWithoutSince
# Description : Outputs list of all symbols without a since tag to a file
#
# Arguments   : none
#############################################################################

sub OutputSymbolsWithoutSince {
     my $n_documented = 0;
     my $total = 0;
     my $symbol;
     my $percent;
     my $msg;

     open (SYMBOLS, ">$ROOT_DIR/$MODULE-nosince.txt")
          || die "Can't create $ROOT_DIR/$MODULE-nosince.txt: $!";

     foreach $symbol (sort (keys (%SourceSymbolDocs))) {
         if (!defined $Since{$symbol}) {
             print SYMBOLS $symbol . "\n";
         }
     }

     close (SYMBOLS);
}


#############################################################################
# Function    : MergeSourceDocumentation
# Description : This merges documentation read from a source file into the
#                documentation read in from a template file.
#
#                Parameter descriptions override any in the template files.
#                Function descriptions are placed before any description from
#                the template files.
#
# Arguments   : none
#############################################################################

sub MergeSourceDocumentation {
    my $symbol;
    my @Symbols;

    if (scalar %SymbolDocs) {
        @Symbols=keys (%SymbolDocs);
        #("num existing entries: ".(scalar @Symbols)."\n");
    }
    else {
        # filter scanned declarations, with what we suppress from -sections.txt
        my %tmp = ();
        foreach $symbol (keys (%Declarations)) {
            if (defined($KnownSymbols{$symbol}) && $KnownSymbols{$symbol} == 1) {
                $tmp{$symbol}=1;
            }
        }
        # , add the rest from -sections.txt
        foreach $symbol (keys (%KnownSymbols)) {
            if ($KnownSymbols{$symbol} == 1) {
                $tmp{$symbol}=1;
            }
        }
        # and add whats found in the source
        foreach $symbol (keys (%SourceSymbolDocs)) {
            $tmp{$symbol}=1;
        }
        @Symbols = keys (%tmp);
        #("num source entries: ".(scalar @Symbols)."\n");
    }
    foreach $symbol (@Symbols) {
        $AllSymbols{$symbol} = 1;

        my $have_tmpl_docs = 0;

        ## see if the symbol is documented in template
        my $tmpl_doc = defined ($SymbolDocs{$symbol}) ? $SymbolDocs{$symbol} : "";
        my $check_tmpl_doc =$tmpl_doc;
        # remove all xml-tags and whitespaces
        $check_tmpl_doc =~ s/<.*?>//g;
        $check_tmpl_doc =~ s/\s//g;
        # anything left ?
        if ($check_tmpl_doc ne "") {
            $have_tmpl_docs = 1;
        } else {
            # if the docs have just an empty para, don't merge that.
            $check_tmpl_doc = $tmpl_doc;
            $check_tmpl_doc =~ s/(\s|\n)//msg;
            if ($check_tmpl_doc eq "<para></para>") {
               $tmpl_doc = "";
            }
        }

        if (exists ($SourceSymbolDocs{$symbol})) {
            my $type = $DeclarationTypes {$symbol};

            #("merging [$symbol] from source\n");

            my $item = "Parameter";
            if (defined ($type)) {
                if ($type eq 'STRUCT') {
                    $item = "Field";
                } elsif ($type eq 'ENUM') {
                    $item = "Value";
                } elsif ($type eq 'UNION') {
                    $item = "Field";
                }
            } else {
                $type="SIGNAL";
            }

            my $src_doc = $SourceSymbolDocs{$symbol};
            # remove leading and training whitespaces
            $src_doc =~ s/^\s+//;
            $src_doc =~ s/\s+$//;

            # Don't output warnings for overridden titles as titles are
            # automatically generated in the -sections.txt file, and thus they
            # are often overridden.
            if ($have_tmpl_docs && $symbol !~ m/:Title$/) {
                # check if content is different
                if ($tmpl_doc ne $src_doc) {
                    #print "[$tmpl_doc] [$src_doc]\n";
                    &LogWarning ($SourceSymbolSourceFile{$symbol}, $SourceSymbolSourceLine{$symbol},
                        "Documentation in template ".$SymbolSourceFile{$symbol}.":".$SymbolSourceLine{$symbol}." for $symbol being overridden by inline comments.");
                }
            }

            if ($src_doc ne "") {
                 $AllDocumentedSymbols{$symbol} = 1;
            }

            # Do not add <para> to nothing, it breaks missing docs checks.
            my $src_doc_para = "";
            if ($src_doc ne "") {
                $src_doc_para = $src_doc;
            }

            if ($symbol =~ m/$TMPL_DIR\/.+:Long_Description/) {
                $SymbolDocs{$symbol} = "$src_doc_para$tmpl_doc";
            } elsif ($symbol =~ m/$TMPL_DIR\/.+:.+/) {
                # For the title/summary/see also section docs we don't want to
                # add any <para> tags.
                $SymbolDocs{$symbol} = "$src_doc"
            } else {
                $SymbolDocs{$symbol} = "$src_doc_para$tmpl_doc";
            }

            # merge parameters
            if ($symbol =~ m/.*::.*/) {
                # For signals we prefer the param names from the source docs,
                # since the ones from the templates are likely to contain the
                # artificial argn names which are generated by gtkdoc-scangobj.
                $SymbolParams{$symbol} = $SourceSymbolParams{$symbol};
                # FIXME: we need to check for empty docs here as well!
            } else {
                # The templates contain the definitive parameter names and order,
                # so we will not change that. We only override the actual text.
                my $tmpl_params = $SymbolParams{$symbol};
                if (!defined ($tmpl_params)) {
                    #("No merge needed for $symbol\n");
                    $SymbolParams{$symbol} = $SourceSymbolParams{$symbol};
                    #  FIXME: we still like to get the number of params and merge
                    #  1) we would noticed that params have been removed/renamed
                    #  2) we would catch undocumented params
                    #  params are not (yet) exported in -decl.txt so that we
                    #  could easily grab them :/
                } else {
                    my $params = $SourceSymbolParams{$symbol};
                    my $j;
                    #("Merge needed for $symbol, tmpl_params: ",$#$tmpl_params,", source_params: ",$#$params," \n");
                    for ($j = 0; $j <= $#$tmpl_params; $j += $PARAM_FIELD_COUNT) {
                        my $tmpl_param_name = $$tmpl_params[$j];

                        # Try to find the param in the source comment documentation.
                        my $found = 0;
                        my $k;
                        #("  try merge param $tmpl_param_name\n");
                        for ($k = 0; $k <= $#$params; $k += $PARAM_FIELD_COUNT) {
                            my $param_name = $$params[$k];
                            my $param_desc = $$params[$k + 1];

                            #("    test param  $param_name\n");
                            # We accept changes in case, since the Gnome source
                            # docs contain a lot of these.
                            if ("\L$param_name" eq "\L$tmpl_param_name") {
                                $found = 1;

                                # Override the description.
                                $$tmpl_params[$j + 1] = $param_desc;

                                # Set the name to "" to mark it as used.
                                $$params[$k] = "";
                                last;
                            }
                        }

                        # If it looks like the parameters are there, but not
                        # in the right place, try to explain a bit better.
                        if ((!$found) && ($src_doc =~ m/\@$tmpl_param_name:/)) {
                            &LogWarning (&GetSymbolSourceFile ($symbol), &GetSymbolSourceLine($symbol),
                                "Parameters for $symbol must start on the line immediately after the function or macro name.");
                        }
                    }

                    # Now we output a warning if parameters have been described which
                    # do not exist.
                    for ($j = 0; $j <= $#$params; $j += $PARAM_FIELD_COUNT) {
                        my $param_name = $$params[$j];
                        if ($param_name) {
                            # the template builder cannot detect if a macro returns
                            # a result or not
                            if(($type eq "MACRO") && ($param_name eq "Returns")) {
                                # FIXME: do we need to add it then to tmpl_params[] ?
                                my $num=$#$tmpl_params;
                                #("  adding Returns: to macro docs for $symbol.\n");
                                $$tmpl_params[$num+1]="Returns";
                                $$tmpl_params[$num+2]=$$params[$j+1];
                                next;
                            }
                            &LogWarning (&GetSymbolSourceFile ($symbol), &GetSymbolSourceLine($symbol),
                                "$item described in source code comment block but does not exist. $type: $symbol $item: $param_name.");
                        }
                    }
                }
            }
        } else {
            if ($have_tmpl_docs) {
                $AllDocumentedSymbols{$symbol} = 1;
                #("merging [$symbol] from template\n");
            }
            else {
                #("[$symbol] undocumented\n");
            }
        }

        # if this symbol is documented, check if docs are complete
        $check_tmpl_doc = defined ($SymbolDocs{$symbol}) ? $SymbolDocs{$symbol} : "";
        # remove all xml-tags and whitespaces
        $check_tmpl_doc =~ s/<.*?>//g;
        $check_tmpl_doc =~ s/\s//g;
        if ($check_tmpl_doc ne "") {
            my $tmpl_params = $SymbolParams{$symbol};
            if (defined ($tmpl_params)) {
                my $type = $DeclarationTypes {$symbol};

                my $item = "Parameter";
                if (defined ($type)) {
                    if ($type eq 'STRUCT') {
                        $item = "Field";
                    } elsif ($type eq 'ENUM') {
                        $item = "Value";
                    } elsif ($type eq 'UNION') {
                        $item = "Field";
                    }
                } else {
                    $type="SIGNAL";
                }

                #("Check param docs for $symbol, tmpl_params: ",$#$tmpl_params," entries, type=$type\n");

                if ($#$tmpl_params > 0) {
                    my $j;
                    for ($j = 0; $j <= $#$tmpl_params; $j += $PARAM_FIELD_COUNT) {
                        # Output a warning if the parameter is empty and
                        # remember for stats.
                        my $tmpl_param_name = $$tmpl_params[$j];
                        my $tmpl_param_desc = $$tmpl_params[$j + 1];
                        if ($tmpl_param_name ne "void" && $tmpl_param_desc !~ m/\S/) {
                            if (exists ($AllIncompleteSymbols{$symbol})) {
                                $AllIncompleteSymbols{$symbol}.=", ".$tmpl_param_name;
                            } else {
                                $AllIncompleteSymbols{$symbol}=$tmpl_param_name;
                            }
                            &LogWarning (&GetSymbolSourceFile ($symbol), &GetSymbolSourceLine($symbol),
                                "$item description for $symbol"."::"."$tmpl_param_name is missing in source code comment block.");
                        }
                    }
                }
                else {
                    if ($#$tmpl_params == 0) {
                        $AllIncompleteSymbols{$symbol}="<items>";
                        &LogWarning (&GetSymbolSourceFile ($symbol), &GetSymbolSourceLine($symbol),
                            "$item descriptions for $symbol are missing in source code comment block.");
                    }
                    # $#$tmpl_params==-1 means we don't know about parameters
                    # this unfortunately does not tell if there should be some
                }
            }
        }
   }
   #("num doc entries: ".(scalar %SymbolDocs)."\n");
}

#############################################################################
# Function    : IsEmptyDoc
# Description : Check if a doc-string is empty. Its also regarded as empty if
#               it only consist of whitespace or e.g. FIXME.
# Arguments   : the doc-string
#############################################################################
sub IsEmptyDoc {
    my ($doc) = @_;

    if ($doc =~ /^\s*$/) {
        return 1;
    }

    if ($doc =~ /^\s*<para>\s*(FIXME)?\s*<\/para>\s*$/) {
        return 1;
    }

    return 0;
}

#############################################################################
# Function    : ConvertMarkDown
# Description : Converts mark down syntax to the respective docbook.
#               http://de.wikipedia.org/wiki/Markdown
#               Inspired by the design of ParseDown
#               http://parsedown.org/
#               Copyright (c) 2013 Emanuil Rusev, erusev.com
# Arguments   : the symbol name, the doc-string
#############################################################################

sub ConvertMarkDown {
    my ($symbol, $text) = @_;

    $text = &MarkDownParse ($text, $symbol);

    return $text
}

# SUPPORTED MARKDOWN
# ==================
#
# Atx-style Headers
# -----------------
#
# # Header 1
#
# ## Header 2 ##
#
# Setext-style Headers
# --------------------
#
# Header 1
# ========
#
# Header 2
# --------
#
# Ordered (unnested) Lists
# ------------------------
#
# 1. item 1
#
# 1. item 2 with loooong
#    description
#
# 3. item 3
#
# Note: we require a blank line above the list items
#

# TODO(ensonic): it would be nice to add id parameters to the refsect2 elements

sub MarkDownParseBlocks {
  my ($linesref, $symbol, $context) = @_;
  my $line;
  my @md_blocks = ();
  my $md_block = { type => "" };

 OUTER: foreach $line (@$linesref) {
    my $first_char = substr ($line, 0, 1);
    my $deindented_line;

    #("in '".$md_block->{"type"}."' state, parsing '$line'");

    if ($md_block->{"type"} eq "markup") {
      if (!$md_block->{"closed"}) {
        if (index ($line, $md_block->{"start"}) != -1) {
          $md_block->{"depth"}++;
        }
        if (index ($line, $md_block->{"end"}) != -1) {
          if ($md_block->{"depth"} > 0) {
            $md_block->{"depth"}--;
          } else {
            #("closing tag '$line'");
            $md_block->{"closed"} = 1;
            # TODO(ensonic): reparse inner text with MarkDownParseLines?
          }
        }
        $md_block->{"text"} .= "\n" . $line;
        #("add to markup");
        next OUTER;
      }
    }

    $deindented_line = $line;
    $deindented_line =~ s/^\s+//;

    if ($md_block->{"type"} eq "heading") {
      # a heading is ended by any level less than or equal
      if ($md_block->{"level"} == 1) {
        if ($line =~ /^={4,}[ \t]*$/) {
          my $text = pop @{$md_block->{"lines"}};
          $md_block->{"interrupted"} = 0;
          push @md_blocks, $md_block;

          $md_block = { type => "heading",
                        text => $text,
                        lines => [],
                        level => 1 };
          next OUTER;
        } elsif ($line =~ /^[#][ \t]+(.+?)[ \t]*[#]*[ \t]*(?:{#([^}]+)})?[ \t]*$/) {
          $md_block->{"interrupted"} = 0;
          push @md_blocks, $md_block;

          $md_block = { type => "heading",
                        text => $1,
                        id => $2,
                        lines => [],
                        level => 1 };
          next OUTER;
        } else {
          # push lines into the block until the end is reached
          push @{$md_block->{"lines"}}, $line;
          next OUTER;
        }
      } else {
        if ($line =~ /^[=]{4,}[ \t]*$/) {
          my $text = pop @{$md_block->{"lines"}};
          $md_block->{"interrupted"} = 0;
          push @md_blocks, $md_block;

          $md_block = { type => "heading",
                        text => $text,
                        lines => [],
                        level => 1 };
          next OUTER;
        } elsif ($line =~ /^[-]{4,}[ \t]*$/) {
          my $text = pop @{$md_block->{"lines"}};
          $md_block->{"interrupted"} = 0;
          push @md_blocks, $md_block;

          $md_block = { type => "heading",
                        text => $text,
                        lines => [],
                        level => 2 };
          next OUTER;
        } elsif ($line =~ /^([#]{1,2})[ \t]+(.+?)[ \t]*[#]*[ \t]*(?:{#([^}]+)})?[ \t]*$/) {
          $md_block->{"interrupted"} = 0;
          push @md_blocks, $md_block;

          $md_block = { type => "heading",
                        text => $2,
                        id => $3,
                        lines => [],
                        level => length($1) };
          next OUTER;
        } else {
          # push lines into the block until the end is reached
          push @{$md_block->{"lines"}}, $line;
          next OUTER;
        }
      }
    } elsif ($md_block->{"type"} eq "code") {
      if ($line =~ /^[ \t]*\]\|(.*)/) {
        push @md_blocks, $md_block;
        $md_block = { type => "paragraph",
                      text => "$1",
                      lines => [] };
      } else {
        push @{$md_block->{"lines"}}, $line;
      }
      next OUTER;
    }

    if ($deindented_line eq "") {
      $md_block->{"interrupted"} = 1;
      next;
    }

    if ($md_block->{"type"} eq "quote") {
      if (!$md_block->{"interrupted"}) {
        $line =~ s/^[ ]*>[ ]?//;
        push @{$md_block->{"lines"}}, $line;
        next OUTER;
      }
    } elsif ($md_block->{"type"} eq "li") {
      my $marker = $md_block->{"marker"};
      if ($line =~ /^([ ]{0,3})($marker)[ ](.*)/) {
        my $indentation = $1;
        if ($md_block->{"indentation"} ne $indentation) {
          push @{$md_block->{"lines"}}, $line;
        } else {
          my $lines = $3;
          my $ordered = $md_block->{"ordered"};
          $lines =~ s/^[ ]{0,4}//;
          $md_block->{"last"} = 0;
          push @md_blocks, $md_block;
          $md_block = { type => "li",
                        ordered => $ordered,
                        indentation => $indentation,
                        marker => $marker,
                        first => 0,
                        last => 1,
                        lines => [ $lines ] };
        }
        next OUTER;
      }

      if ($md_block->{"interrupted"}) {
        if ($first_char eq " ") {
          push @{$md_block->{"lines"}}, "";
          $line =~ s/^[ ]{0,4}//;
          push @{$md_block->{"lines"}}, $line;
          $md_block->{"interrupted"} = 0;
          next OUTER;
        }
      } else {
        $line =~ s/^[ ]{0,4}//;
        push @{$md_block->{"lines"}}, $line;
        next OUTER;
      }
    }

    # indentation sensitive types
    #("parsing '$line'");

    if ($line =~ /^([#]{1,2})[ \t]+(.+?)[ \t]*[#]*[ \t]*(?:{#([^}]+)})?[ \t]*$/) {
      # atx heading (#)
      push @md_blocks, $md_block;

      $md_block = { type => "heading",
                    text => $2,
                    id => $3,
                    lines => [],
                    level => length($1) };

      next OUTER;
    } elsif ($line =~ /^={4,}[ \t]*$/) {
      # setext heading (====)

      if ($md_block->{"type"} eq "paragraph" && $md_block->{"interrupted"}) {
        push @md_blocks, $md_block;
        $md_block->{"type"} = "heading";
        $md_block->{"lines"} = [];
        $md_block->{"level"} = 1;
      }

      next OUTER;
    } elsif ($line =~ /^-{4,}[ \t]*$/) {
      # setext heading (-----)

      if ($md_block->{"type"} eq "paragraph" && $md_block->{"interrupted"}) {
        push @md_blocks, $md_block;
        $md_block->{"type"} = "heading";
        $md_block->{"lines"} = [];
        $md_block->{"level"} = 2;
      }

      next OUTER;
    } elsif ($line =~ /^[ \t]*\|\[[ ]*(?:<!-- language="([^"]+?)" -->)?/) {
      # code
      $md_block->{"interrupted"} = 1;
      push @md_blocks, $md_block;
      $md_block = { type => "code",
                    language => $1,
                    lines => [] };
      next OUTER;
    }

    # indentation insensitive types
    if ($line =~ /^[ ]*<!DOCTYPE/) {
      push @md_blocks, $md_block;

      $md_block = { type   => "markup",
                    text   => $deindented_line,
                    start  => "<",
                    end    => ">",
                    closed => 0,
                    depth  => 0 };

    } elsif ($line =~ /^[ ]*<\??(\w+)[^>]*([\/\?])?[ \t]*>/) {
      # markup, including <?xml version="1.0"?>
      my $tag = $1;
      my $is_self_closing = defined($2);
      
      # skip link markdown
      # TODO(ensonic): consider adding more uri schemes (ftp, ...)
      if ($tag =~ /^https?/) {
        #("skipping link '$tag'");
      } else {
        # for TEXT_LEVEL_ELEMENTS, we want to keep them as-is in the paragraph
        # instead of creation a markdown block.
        my $scanning_for_end_of_text_level_tag = (
            $md_block->{"type"} eq "paragraph" && 
            defined($md_block->{"start"}) &&
            !$md_block->{"closed"}); 
        #("markup found '$tag', scanning $scanning_for_end_of_text_level_tag ?");
        if (!$MD_TEXT_LEVEL_ELEMENTS{$tag} && !$scanning_for_end_of_text_level_tag) {
          push @md_blocks, $md_block;
  
          if ($is_self_closing) {
            #("self-closing docbook '$tag'");
            $md_block = { type => "self-closing tag",
                          text => $deindented_line };
            $is_self_closing = 0;
            next OUTER;
          }
  
          #("new markup '$tag'");
          $md_block = { type   => "markup",
                        text   => $deindented_line,
                        start  => "<" . $tag . ">",
                        end    => "</" . $tag . ">",
                        closed => 0,
                        depth  => 0 };
          if ($deindented_line =~ /<\/$tag>/) {
            $md_block->{"closed"} = 1;
          }
          next OUTER;
        } else {
          if ($MD_TEXT_LEVEL_ELEMENTS{$tag}) {
            #("text level docbook '$tag' in '".$md_block->{"type"}."' state");
            # TODO(ensonic): handle nesting
            if (!$scanning_for_end_of_text_level_tag) {
              if ($deindented_line !~ /<\/$tag>/) {
                #("new text level markup '$tag'");
                $md_block->{"start"} = "<" . $tag . ">";
                $md_block->{"end"} = "</" . $tag . ">";
                $md_block->{"closed"} = 0;
                #("scanning for end of '$tag'");
              }
            } else {
              if ($deindented_line =~ /$md_block->{"end"}/) {
                $md_block->{"closed"} = 1;
                #("found end of '$tag'");
              }
            }
          }
        }
      }
    } elsif ($line =~ /^([ ]*)[*+-][ ](.*)/) {
      # li
      push @md_blocks, $md_block;
      my $lines = $2;
      my $indentation = $1;
      $lines =~ s/^[ ]{0,4}//;
      $md_block = { type => "li",
                    ordered => 0,
                    indentation => $indentation,
                    marker => "[*+-]",
                    first => 1,
                    last => 1,
                    lines => [ $lines ] };
      next OUTER;
    } elsif ($line =~ /^[ ]*>[ ]?(.*)/) {
      push @md_blocks, $md_block;
      $md_block = { type => "quote",
                    lines => [ $1 ] };
      next OUTER;
    }

    # list item
    if ($line =~ /^([ ]{0,4})\d+[.][ ]+(.*)/) {
      push @md_blocks, $md_block;
      my $lines = $2;
      my $indentation = $1;
      $lines =~ s/^[ ]{0,4}//;

      $md_block = { type => "li",
                    ordered => 1,
                    indentation => $indentation,
                    marker => "\\d+[.]",
                    first => 1,
                    last => 1,
                    lines => [ $lines ] };

      next;
    }

    # paragraph
    if ($md_block->{"type"} eq "paragraph") {
      if ($md_block->{"interrupted"}) {
        push @md_blocks, $md_block;
        $md_block = { type => "paragraph",
                      interrupted => 0,
                      text => $line };
        #("new paragraph due to interrupted");
      } else {
        $md_block->{"text"} .= "\n" . $line;
        #("add to paragraph");
      }
    } else {
      push @md_blocks, $md_block;
      $md_block = { type => "paragraph",
                    text => $line };
      #("new paragraph due to different block type");
    }
  }

  push @md_blocks, $md_block;

  shift @md_blocks;

  return @md_blocks;
}

sub MarkDownParseSpanElementsInner {
  my ($text, $markersref) = @_;
  my $markup = "";
  my %markers = map { $_ => 1 } @$markersref;

  while ($text ne "") {
    my $closest_marker = "";
    my $closest_marker_index = 0;
    my $closest_marker_position = -1;
    my $text_marker = "";
    my $i = 0;
    my $offset = 0;
    my @markers_rest;
    my $marker;
    my $use;

    while ( ($marker, $use) = each %markers ) {
      my $marker_position;

      if (!$use) {
        next;
      }

      $marker_position = index ($text, $marker);

      if ($marker_position < 0) {
        $markers{$marker} = 0;
        next;
      }

      if ($closest_marker eq "" || $marker_position < $closest_marker_position) {
        $closest_marker = $marker;
        $closest_marker_index = $i;
        $closest_marker_position = $marker_position;
      }
    }

    if ($closest_marker_position >= 0) {
      $text_marker = substr ($text, $closest_marker_position);
    }

    if ($text_marker eq "") {
      $markup .= $text;
      $text = "";
      next; # last
    }

    $markup .= substr ($text, 0, $closest_marker_position);
    $text = substr ($text, $closest_marker_position);
    @markers_rest = map { $markers{$_} ? ($_ eq $closest_marker ? () : $_) : () } keys %markers;

    if ($closest_marker eq "![" || $closest_marker eq "[") {
      my %element;

      if (index ($text, "]") && $text =~ /\[((?:[^][]|(?R))*)\]/) {
        my $remaining_text;

        %element = ( "!" => (substr ($text, 0, 1) eq "!"),
                     "a" => $1 );

        $offset = length ($&);
        if ($element{"!"}) {
          $offset++;
        }

        $remaining_text = substr ($text, $offset);
        if ($remaining_text =~ /^\([ ]*([^)'"]*?)(?:[ ]+['"](.+?)['"])?[ ]*\)/) {
          $element{"»"} = $1;
          if (defined ($2)) {
            $element{"#"} = $2;
          }
          $offset += length ($&);
        } elsif ($remaining_text =~ /^\s*\[([^\]<]*?)\]/) {
          $element{"ref"} = $1;
          $offset += length ($&);
        } else {
          undef %element;
        }
      }

      if (%element) {
        if ($element{"»"}) {
          $element{"»"} =~ s/&/&amp;/g;
          $element{"»"} =~ s/</&lt;/g;
        }
        if ($element{"!"}) {
          $markup .= "<inlinemediaobject><imageobject><imagedata fileref=\"" . $element{"»"} . "\"></imagedata></imageobject>";

          if (defined ($element{"a"})) {
            $markup .= "<textobject><phrase>" . $element{"a"} . "</phrase></textobject>";
          }

          $markup .= "</inlinemediaobject>";
        } elsif ($element{"ref"}) {
          $element{"a"} = &MarkDownParseSpanElementsInner ($element{"a"}, \@markers_rest);
          $markup .= "<link linkend=\"" . $element{"ref"} . "\"";

          if (defined ($element{"#"})) {
            # title attribute not supported
          }

          $markup .= ">" . $element{"a"} . "</link>";
        } else {
          $element{"a"} = &MarkDownParseSpanElementsInner ($element{"a"}, \@markers_rest);
          $markup .= "<ulink url=\"" . $element{"»"} . "\"";

          if (defined ($element{"#"})) {
            # title attribute not supported
          }

          $markup .= ">" . $element{"a"} . "</ulink>";
        }
      } else {
        $markup .= $closest_marker;
        if ($closest_marker eq "![") {
          $offset = 2;
        } else {
          $offset = 1;
        }
      }
    } elsif ($closest_marker eq "<") {
      if ($text =~ /^<(https?:[\/]{2}[^\s]+?)>/i) {
        my $element_url = $1;
        $element_url =~ s/&/&amp;/g;
        $element_url =~ s/</&lt;/g;

        $markup .= "<ulink url=\"" . $element_url . "\">" . $element_url . "</ulink>";
        $offset = length ($&);
      } elsif ($text =~ /^<([A-Za-z0-9._-]+?@[A-Za-z0-9._-]+?)>/) {
        $markup .= "<ulink url=\"mailto:" . $1 . "\">" . $1 . "</ulink>";
        $offset = length ($&);
      } elsif ($text =~ /^<[^>]+?>/) {
        $markup .= $&;
        $offset = length ($&);
      } else {
        $markup .= "&lt;";
        $offset = 1;
      }
    } elsif ($closest_marker eq "\\") {
      my $special_char = substr ($text, 1, 1);
      if ($MD_ESCAPABLE_CHARS{$special_char} ||
          $MD_GTK_ESCAPABLE_CHARS{$special_char}) {
        $markup .= $special_char;
        $offset = 2;
      } else {
        $markup .= "\\";
        $offset = 1;
      }
    } elsif ($closest_marker eq "`") {
      if ($text =~ /^(`+)([^`]+?)\1(?!`)/) {
        my $element_text = $2;
        $markup .= "<literal>" . $element_text . "</literal>";
        $offset = length ($&);
      } else {
        $markup .= "`";
        $offset = 1;
      }
    } elsif ($closest_marker eq "@") {
      # Convert '@param()'
      # FIXME: we could make those also links ($symbol.$2), but that would be less
      # useful as the link target is a few lines up or down
      if ($text =~ /^(\A|[^\\])\@(\w+((\.|->)\w+)*)\s*\(\)/) {
        $markup .= $1 . "<parameter>" . $2 . "()</parameter>\n";
        $offset = length ($&);
      } elsif ($text =~ /^(\A|[^\\])\@(\w+((\.|->)\w+)*)/) {
        # Convert '@param', but not '\@param'.
        $markup .= $1 . "<parameter>" . $2 . "</parameter>\n";
        $offset = length ($&);
      } elsif ($text =~ /^\\\@/) {
        $markup .= "\@";
        $offset = length ($&);
      } else {
        $markup .= "@";
        $offset = 1;
      }
    } elsif ($closest_marker eq "#") {
      if ($text =~ /^(\A|[^\\])#([\w\-:\.]+[\w]+)\s*\(\)/) {
        # handle #Object.func()
        $markup .= $1 . &MakeXRef ($2, &tagify ($2 . "()", "function"));
        $offset = length ($&);
      } elsif ($text =~ /^(\A|[^\\])#([\w\-:\.]+[\w]+)/) {
        # Convert '#symbol', but not '\#symbol'.
        $markup .= $1 . &MakeHashXRef ($2, "type");
        $offset = length ($&);
      } elsif ($text =~ /^\\#/) {
        $markup .= "#";
        $offset = length ($&);
      } else {
        $markup .= "#";
        $offset = 1;
      }
    } elsif ($closest_marker eq "%") {
      if ($text =~ /^(\A|[^\\])\%(-?\w+)/) {
        # Convert '%constant', but not '\%constant'.
        # Also allow negative numbers, e.g. %-1.
        $markup .= $1 . &MakeXRef ($2, &tagify ($2, "literal"));
        $offset = length ($&);
      } elsif ($text =~ /^\\%/) {
        $markup .= "\%";
        $offset = length ($&);
      } else {
        $markup .= "%";
        $offset = 1;
      }
    }

    if ($offset > 0) {
      $text = substr ($text, $offset);
    }
  }

  return $markup;
}

sub MarkDownParseSpanElements {
  my ($text) = @_;
  my @markers = ( "\\", "<", "![", "[", "`", "%", "#", "@" );

  $text = &MarkDownParseSpanElementsInner ($text, \@markers);

  # Convert 'function()' or 'macro()'.
  # if there is abc_*_def() we don't want to make a link to _def()
  # FIXME: also handle abc(def(....)) : but that would need to be done recursively :/
  $text =~ s/([^\*.\w])(\w+)\s*\(\)/$1.&MakeXRef($2, &tagify($2 . "()", "function"));/eg;

  return $text;
}

sub ReplaceEntities {
  my ($text, $symbol) = @_;
  my $warn = "";
  my @entities = ( [ "&lt;", "<" ],
                   [ "&gt;", ">" ],
                   [ "&ast;", "*" ],
                   [ "&num;", "#" ],
                   [ "&percnt;", "%"],
                   [ "&colon;", ":" ],
                   [ "&quot;", "\"" ],
                   [ "&apos;", "'" ],
                   [ "&nbsp;", " " ],
                   [ "&amp;", "&" ] ); # Do this last, or the others get messed up.
  my $i;

  # Expand entities in <programlisting> even inside CDATA since
  # we changed the definition of |[ to add CDATA
  for ($i = 0; $i <= $#entities; $i++) {
    $text =~ s/$entities[$i][0]/$entities[$i][1]/g;
  }

  return $text;
}

sub MarkDownOutputDocBook {
  my ($blocksref, $symbol, $context) = @_;
  my $output = "";
  my $block;
  my @blocks = @$blocksref;

  foreach $block (@blocks) {
    my $text;
    my $title;

    #$output .= "\n<!-- beg type='" . $block->{"type"} . "'-->\n";

    if ($block->{"type"} eq "paragraph") {
      $text = &MarkDownParseSpanElements ($block->{"text"});
      if ($context eq "li" && $output eq "") {
        if ($block->{"interrupted"}) {
          $output .= "\n<para>$text</para>\n";
        } else {
          $output .= "<para>$text</para>";
          if ($#blocks > 0) {
            $output .= "\n";
          }
        }
      } else {
        $output .= "<para>$text</para>\n";
      }

    } elsif ($block->{"type"} eq "heading") {
      my $tag;

      $title = &MarkDownParseSpanElements ($block->{"text"});

      if ($block->{"level"} == 1) {
        $tag = "refsect2";
      } else {
        $tag = "refsect3";
      }

      $text = &MarkDownParseLines ($block->{"lines"}, $symbol, "heading");
      if (defined ($block->{"id"})) {
        $output .= "<$tag id=\"" . $block->{"id"} . "\">";
      } else {
        $output .= "<$tag>";
      }

      $output .= "<title>$title</title>$text</$tag>\n";
    } elsif ($block->{"type"} eq "li") {
      my $tag = "itemizedlist";

      if ($block->{"first"}) {
        if ($block->{"ordered"}) {
          $tag = "orderedlist";
        }
        $output .= "<$tag>\n";
      }

      if ($block->{"interrupted"}) {
        push @{$block->{"lines"}}, "";
      }

      $text = &MarkDownParseLines ($block->{"lines"}, $symbol, "li");
      $output .= "<listitem>".$text."</listitem>\n";
      if ($block->{"last"}) {
        if ($block->{"ordered"}) {
          $tag = "orderedlist";
        }
        $output .= "</$tag>\n";
      }
    } elsif ($block->{"type"} eq "quote") {
      $text = &MarkDownParseLines ($block->{"lines"}, $symbol, "quote");
      $output .= "<blockquote>\n$text</blockquote>\n";
    } elsif ($block->{"type"} eq "code") {
      my $tag = "programlisting";

      if ($block->{"language"}) {
        if ($block->{"language"} eq "plain") {
          $output .= "<informalexample><screen><![CDATA[\n";
          $tag = "screen";
        } else {
          $output .= "<informalexample><programlisting language=\"" . $block->{"language"} . "\"><![CDATA[\n";
        }
      } else {
        $output .= "<informalexample><programlisting><![CDATA[\n";
      }
      foreach (@{$block->{"lines"}}) {
        $output .= &ReplaceEntities ($_, $symbol) . "\n";
      }
      $output .= "]]></$tag></informalexample>\n";
    } elsif ($block->{"type"} eq "markup") {
      $text = &ExpandAbbreviations($symbol, $block->{"text"});
      $output .= $text."\n";
    } else {
      $output .= $block->{"text"}."\n";
    }
    #$output .= "\n<!-- end type='" . $block->{"type"} . "'-->\n";
  }

  return $output;
}

sub MarkDownParseLines {
  my ($linesref, $symbol, $context) = @_;
  my $output;
  my @lines = @$linesref;
  my @blocks;

  @blocks = &MarkDownParseBlocks (\@lines, $symbol, $context);
  $output = &MarkDownOutputDocBook (\@blocks, $symbol, $context);

  return $output;
}

sub MarkDownParse {
  my ($text, $symbol) = @_;
  my @lines;

  # take out some variability in line endings
  $text =~ s%\r\n%\n%g;
  $text =~ s%\r%\n%g;

  # split lines
  @lines = split("\n", $text);
  $text = MarkDownParseLines(\@lines, $symbol, "");

  return $text;
}

#############################################################################
# LIBRARY FUNCTIONS -        These functions are used in both gtkdoc-mkdb and
#                        gtkdoc-mktmpl and should eventually be moved to a
#                        separate library.
#############################################################################

#############################################################################
# Function    : ReadDeclarationsFile
# Description : This reads in a file containing the function/macro/enum etc.
#                declarations.
#
#                Note that in some cases there are several declarations with
#                the same name, e.g. for conditional macros. In this case we
#                set a flag in the %DeclarationConditional hash so the
#                declaration is not shown in the docs.
#
#                If a macro and a function have the same name, e.g. for
#                gtk_object_ref, the function declaration takes precedence.
#
#                Some opaque structs are just declared with 'typedef struct
#                _name name;' in which case the declaration may be empty.
#                The structure may have been found later in the header, so
#                that overrides the empty declaration.
#
# Arguments   : $file - the declarations file to read
#                $override - if declarations in this file should override
#                        any current declaration.
#############################################################################

sub ReadDeclarationsFile {
    my ($file, $override) = @_;

    if ($override == 0) {
        %Declarations = ();
        %DeclarationTypes = ();
        %DeclarationConditional = ();
        %DeclarationOutput = ();
    }

    open (INPUT, $file)
        || die "Can't open $file: $!";
    my $declaration_type = "";
    my $declaration_name;
    my $declaration;
    my $is_deprecated = 0;
    while (<INPUT>) {
        if (!$declaration_type) {
            if (m/^<([^>]+)>/) {
                $declaration_type = $1;
                $declaration_name = "";
                #("Found declaration: $declaration_type\n");
                $declaration = "";
            }
        } else {
            if (m%^<NAME>(.*)</NAME>%) {
                $declaration_name = $1;
            } elsif (m%^<DEPRECATED/>%) {
                $is_deprecated = 1;
            } elsif (m%^</$declaration_type>%) {
                #("Found end of declaration: $declaration_name\n");
                # Check that the declaration has a name
                if ($declaration_name eq "") {
                    &LogWarning ($file, $., "$declaration_type has no name.\n");
                }

                # If the declaration is an empty typedef struct _XXX XXX
                # set the flag to indicate the struct has a typedef.
                if (($declaration_type eq 'STRUCT' || $declaration_type eq 'UNION')
                    && $declaration =~ m/^\s*$/) {
                    #("Struct has typedef: $declaration_name\n");
                    $StructHasTypedef{$declaration_name} = 1;
                }

                # Check if the symbol is already defined.
                if (defined ($Declarations{$declaration_name})
                    && $override == 0) {
                    # Function declarations take precedence.
                    if ($DeclarationTypes{$declaration_name} eq 'FUNCTION') {
                        # Ignore it.
                    } elsif ($declaration_type eq 'FUNCTION') {
                        if ($is_deprecated) {
                            $Deprecated{$declaration_name} = "";
                        }
                        $Declarations{$declaration_name} = $declaration;
                        $DeclarationTypes{$declaration_name} = $declaration_type;
                    } elsif ($DeclarationTypes{$declaration_name}
                              eq $declaration_type) {
                        # If the existing declaration is empty, or is just a
                        # forward declaration of a struct, override it.
                        if ($declaration_type eq 'STRUCT' || $declaration_type eq 'UNION') {
                            if ($Declarations{$declaration_name} =~ m/^\s*((struct|union)\s+\w+\s*;)?\s*$/) {
                                if ($is_deprecated) {
                                    $Deprecated{$declaration_name} = "";
                                }
                                $Declarations{$declaration_name} = $declaration;
                            } elsif ($declaration =~ m/^\s*((struct|union)\s+\w+\s*;)?\s*$/) {
                                # Ignore an empty or forward declaration.
                            } else {
                                &LogWarning ($file, $., "Structure $declaration_name has multiple definitions.");
                            }
                        } else {
                            # set flag in %DeclarationConditional hash for
                            # multiply defined macros/typedefs.
                            $DeclarationConditional{$declaration_name} = 1;
                        }
                    } else {
                        &LogWarning ($file, $., "$declaration_name has multiple definitions.");
                    }
                } else {
                    if ($is_deprecated) {
                        $Deprecated{$declaration_name} = "";
                    }
                    $Declarations{$declaration_name} = $declaration;
                    $DeclarationTypes{$declaration_name} = $declaration_type;
                }

                $declaration_type = "";
                $is_deprecated = 0;
            } else {
                $declaration .= $_;
            }
        }
    }
    close (INPUT);
}


#############################################################################
# Function    : ReadSignalsFile
# Description : This reads in an existing file which contains information on
#                all GTK signals. It creates the arrays @SignalNames and
#                @SignalPrototypes containing info on the signals. The first
#                line of the SignalPrototype is the return type of the signal
#                handler. The remaining lines are the parameters passed to it.
#                The last parameter, "gpointer user_data" is always the same
#                so is not included.
# Arguments   : $file - the file containing the signal handler prototype
#                        information.
#############################################################################

sub ReadSignalsFile {
    my ($file) = @_;

    my $in_signal = 0;
    my $signal_object;
    my $signal_name;
    my $signal_returns;
    my $signal_flags;
    my $signal_prototype;

    # Reset the signal info.
    @SignalObjects = ();
    @SignalNames = ();
    @SignalReturns = ();
    @SignalFlags = ();
    @SignalPrototypes = ();

    if (! -f $file) {
        return;
    }
    if (!open (INPUT, $file)) {
        warn "Can't open $file - skipping signals\n";
        return;
    }
    while (<INPUT>) {
        if (!$in_signal) {
            if (m/^<SIGNAL>/) {
                $in_signal = 1;
                $signal_object = "";
                $signal_name = "";
                $signal_returns = "";
                $signal_prototype = "";
            }
        } else {
            if (m/^<NAME>(.*)<\/NAME>/) {
                $signal_name = $1;
                if ($signal_name =~ m/^(.*)::(.*)$/) {
                    $signal_object = $1;
                    ($signal_name = $2) =~ s/_/-/g;
                    #("Found signal: $signal_name\n");
                } else {
                    &LogWarning ($file, $., "Invalid signal name: $signal_name.");
                }
            } elsif (m/^<RETURNS>(.*)<\/RETURNS>/) {
                $signal_returns = $1;
            } elsif (m/^<FLAGS>(.*)<\/FLAGS>/) {
                $signal_flags = $1;
            } elsif (m%^</SIGNAL>%) {
                #("Found end of signal: ${signal_object}::${signal_name}\nReturns: ${signal_returns}\n${signal_prototype}");
                push (@SignalObjects, $signal_object);
                push (@SignalNames, $signal_name);
                push (@SignalReturns, $signal_returns);
                push (@SignalFlags, $signal_flags);
                push (@SignalPrototypes, $signal_prototype);
                $in_signal = 0;
            } else {
                $signal_prototype .= $_;
            }
        }
    }
    close (INPUT);
}


#############################################################################
# Function    : ReadTemplateFile
# Description : This reads in the manually-edited documentation file
#               corresponding to the file currently being created, so we can
#               insert the documentation at the appropriate places.
#               It outputs %SymbolTypes, %SymbolDocs and %SymbolParams, which
#               is a hash of arrays.
#               NOTE: This function is duplicated in gtkdoc-mktmpl (but
#               slightly different).
# Arguments   : $docsfile - the template file to read in.
#               $skip_unused_params - 1 if the unused parameters should be
#                 skipped.
#############################################################################

sub ReadTemplateFile {
    my ($docsfile, $skip_unused_params) = @_;

    my $template = "$docsfile.sgml";
    if (! -f $template) {
        #("File doesn't exist: $template\n");
        return 0;
    }

    # start with empty hashes, we merge the source comment for each file
    # afterwards
    %SymbolDocs = ();
    %SymbolTypes = ();
    %SymbolParams = ();

    my $current_type = "";        # Type of symbol being read.
    my $current_symbol = "";        # Name of symbol being read.
    my $symbol_doc = "";                # Description of symbol being read.
    my @params;                        # Parameter names and descriptions of current
                                #   function/macro/function typedef.
    my $current_param = -1;        # Index of parameter currently being read.
                                #   Note that the param array contains pairs
                                #   of param name & description.
    my $in_unused_params = 0;        # True if we are reading in the unused params.
    my $in_deprecated = 0;
    my $in_since = 0;
    my $in_stability = 0;

    open (DOCS, "$template")
        || die "Can't open $template: $!";

    #("reading template $template");

    while (<DOCS>) {
        if (m/^<!-- ##### ([A-Z_]+) (\S+) ##### -->/) {
            my $type = $1;
            my $symbol = $2;
            if ($symbol eq "Title"
                || $symbol eq "Short_Description"
                || $symbol eq "Long_Description"
                || $symbol eq "See_Also"
                || $symbol eq "Stability_Level"
                || $symbol eq "Include"
                || $symbol eq "Image") {

                $symbol = $docsfile . ":" . $symbol;
            }

            #("Found symbol: $symbol\n");
            # Remember file and line for the symbol
            $SymbolSourceFile{$symbol} = $template;
            $SymbolSourceLine{$symbol} = $.;

            # Store previous symbol, but remove any trailing blank lines.
            if ($current_symbol ne "") {
                $symbol_doc =~ s/\s+$//;
                $SymbolTypes{$current_symbol} = $current_type;
                $SymbolDocs{$current_symbol} = $symbol_doc;

                # Check that the stability level is valid.
                if ($StabilityLevel{$current_symbol}) {
                    $StabilityLevel{$current_symbol} = &ParseStabilityLevel($StabilityLevel{$current_symbol}, $template, $., "Stability level for $current_symbol");
                }

                if ($current_param >= 0) {
                    $SymbolParams{$current_symbol} = [ @params ];
                } else {
                    # Delete any existing params in case we are overriding a
                    # previously read template.
                    delete $SymbolParams{$current_symbol};
                }
            }
            $current_type = $type;
            $current_symbol = $symbol;
            $current_param = -1;
            $in_unused_params = 0;
            $in_deprecated = 0;
            $in_since = 0;
            $in_stability = 0;
            $symbol_doc = "";
            @params = ();

        } elsif (m/^<!-- # Unused Parameters # -->/) {
            #("Found unused parameters\n");
            $in_unused_params = 1;
            next;

        } elsif ($in_unused_params && $skip_unused_params) {
            # When outputting the DocBook we skip unused parameters.
            #("Skipping unused param: $_");
            next;

        } else {
            # Check if param found. Need to handle "..." and "format...".
            if (s/^\@([\w\.]+):\040?//) {
                my $param_name = $1;
                my $param_desc = $_;
                # Allow variations of 'Returns'
                if ($param_name =~ m/^[Rr]eturns?$/) {
                    $param_name = "Returns";
                }
                # Allow varargs variations
                if ($param_name =~ m/^.*\.\.\.$/) {
                    $param_name = "...";
                }

                # strip trailing whitespaces and blank lines
                s/\s+\n$/\n/m;
                s/\n+$/\n/sm;
                #("Found param for symbol $current_symbol : '$param_name'= '$_'");

                if ($param_name eq "Deprecated") {
                    $in_deprecated = 1;
                    $Deprecated{$current_symbol} = $_;
                } elsif ($param_name eq "Since") {
                    $in_since = 1;
                    chomp;
                    $Since{$current_symbol} = $_;
                } elsif ($param_name eq "Stability") {
                    $in_stability = 1;
                    $StabilityLevel{$current_symbol} = $_;
                } else {
                    push (@params, $param_name);
                    push (@params, $param_desc);
                    $current_param += $PARAM_FIELD_COUNT;
                }
            } else {
                # strip trailing whitespaces and blank lines
                s/\s+\n$/\n/m;
                s/\n+$/\n/sm;

                if (!m/^\s+$/) {
                    if ($in_deprecated) {
                        $Deprecated{$current_symbol} .= $_;
                    } elsif ($in_since) {
                        &LogWarning ($template, $., "multi-line since docs found");
                        #$Since{$current_symbol} .= $_;
                    } elsif ($in_stability) {
                        $StabilityLevel{$current_symbol} .= $_;
                    } elsif ($current_param >= 0) {
                        $params[$current_param] .= $_;
                    } else {
                        $symbol_doc .= $_;
                    }
                }
            }
        }
    }

    # Remember to finish the current symbol doccs.
    if ($current_symbol ne "") {

        $symbol_doc =~ s/\s+$//;
        $SymbolTypes{$current_symbol} = $current_type;
        $SymbolDocs{$current_symbol} = $symbol_doc;

        # Check that the stability level is valid.
        if ($StabilityLevel{$current_symbol}) {
            $StabilityLevel{$current_symbol} = &ParseStabilityLevel($StabilityLevel{$current_symbol}, $template, $., "Stability level for $current_symbol");
        }

        if ($current_param >= 0) {
            $SymbolParams{$current_symbol} = [ @params ];
        } else {
            # Delete any existing params in case we are overriding a
            # previously read template.
            delete $SymbolParams{$current_symbol};
        }
    }

    close (DOCS);
    return 1;
}


#############################################################################
# Function    : ReadObjectHierarchy
# Description : This reads in the $MODULE-hierarchy.txt file containing all
#               the GtkObject subclasses described in this module (and their
#               ancestors).
#               It places them in the @Objects array, and places their level
#               in the object hierarchy in the @ObjectLevels array, at the
#               same index. GtkObject, the root object, has a level of 1.
#
#               This also generates tree_index.sgml as it goes along.
#
# Arguments   : none
#############################################################################

sub ReadObjectHierarchy {
    @Objects = ();
    @ObjectLevels = ();

    if (! -f $OBJECT_TREE_FILE) {
        return;
    }
    if (!open (INPUT, $OBJECT_TREE_FILE)) {
        warn "Can't open $OBJECT_TREE_FILE - skipping object tree\n";
        return;
    }

    # Only emit objects if they are supposed to be documented, or if
    # they have documented children. To implement this, we maintain a
    # stack of pending objects which will be emitted if a documented
    # child turns up.
    my @pending_objects = ();
    my @pending_levels = ();
    my $root;
    my @tree = ();
    while (<INPUT>) {
        if (m/\S+/) {
            my $object = $&;
            my $level = (length($`)) / 2 + 1;
            my $xref = "";

            if ($level == 1) {
                $root = $object;
            }

            while (($#pending_levels >= 0) && ($pending_levels[$#pending_levels] >= $level)) {
                my $pobject = pop(@pending_objects);
                my $plevel = pop(@pending_levels);
            }

            push (@pending_objects, $object);
            push (@pending_levels, $level);

            if (exists($KnownSymbols{$object})) {
                while ($#pending_levels >= 0) {
                    $object = shift @pending_objects;
                    $level = shift @pending_levels;
                    $xref = &MakeXRef ($object);

                    push (@tree, ' ' x ($level * 4) . "$xref");
                    push (@Objects, $object);
                    push (@ObjectLevels, $level);
                    $ObjectRoots{$object} = $root;
                }
            }
            #else {
            #    LogWarning ($OBJECT_TREE_FILE, $., "unknown type $object");
            #}
        }
    }
    close (INPUT);

    # FIXME: use xml
    # my $old_tree_index = "$DB_OUTPUT_DIR/tree_index.$xml";
    my $old_tree_index = "$DB_OUTPUT_DIR/tree_index.sgml";
    my $new_tree_index = "$DB_OUTPUT_DIR/tree_index.new";

    open (OUTPUT, ">$new_tree_index")
        || die "Can't create $new_tree_index: $!";

    print (OUTPUT &MakeDocHeader ("screen")."\n<screen>\n".&AddTreeLineArt(\@tree)."\n</screen>\n");
    close (OUTPUT);

    &UpdateFileIfChanged ($old_tree_index, $new_tree_index, 0);

    &OutputObjectList;
}

#############################################################################
# Function    : ReadInterfaces
# Description : This reads in the $MODULE.interfaces file.
#
# Arguments   : none
#############################################################################

sub ReadInterfaces {
    %Interfaces = ();

    if (! -f $INTERFACES_FILE) {
        return;
    }
    if (!open (INPUT, $INTERFACES_FILE)) {
        warn "Can't open $INTERFACES_FILE - skipping interfaces\n";
        return;
    }

    while (<INPUT>) {
       chomp;
       my ($object, @ifaces) = split;
       if (exists($KnownSymbols{$object}) && $KnownSymbols{$object} == 1) {
           my @knownIfaces = ();

           # filter out private interfaces, but leave foreign interfaces
           foreach my $iface (@ifaces) {
               if (!exists($KnownSymbols{$iface}) || $KnownSymbols{$iface} == 1) {
                   push (@knownIfaces, $iface);
               }
             }

           $Interfaces{$object} = join(' ', @knownIfaces);
           #("Interfaces for $object: $Interfaces{$object}\n");
       } else {
         #("skipping interfaces for unknown symbol: $object\n");
       }
    }
    close (INPUT);
}

#############################################################################
# Function    : ReadPrerequisites
# Description : This reads in the $MODULE.prerequisites file.
#
# Arguments   : none
#############################################################################

sub ReadPrerequisites {
    %Prerequisites = ();

    if (! -f $PREREQUISITES_FILE) {
        return;
    }
    if (!open (INPUT, $PREREQUISITES_FILE)) {
        warn "Can't open $PREREQUISITES_FILE - skipping prerequisites\n";
        return;
    }

    while (<INPUT>) {
       chomp;
       my ($iface, @prereqs) = split;
       if (exists($KnownSymbols{$iface}) && $KnownSymbols{$iface} == 1) {
           my @knownPrereqs = ();

           # filter out private prerequisites, but leave foreign prerequisites
           foreach my $prereq (@prereqs) {
               if (!exists($KnownSymbols{$prereq}) || $KnownSymbols{$prereq} == 1) {
                  push (@knownPrereqs, $prereq);
               }
           }

           $Prerequisites{$iface} = join(' ', @knownPrereqs);
       }
    }
    close (INPUT);
}

#############################################################################
# Function    : ReadArgsFile
# Description : This reads in an existing file which contains information on
#                all GTK args. It creates the arrays @ArgObjects, @ArgNames,
#                @ArgTypes, @ArgFlags, @ArgNicks and @ArgBlurbs containing info
#               on the args.
# Arguments   : $file - the file containing the arg information.
#############################################################################

sub ReadArgsFile {
    my ($file) = @_;

    my $in_arg = 0;
    my $arg_object;
    my $arg_name;
    my $arg_type;
    my $arg_flags;
    my $arg_nick;
    my $arg_blurb;
    my $arg_default;
    my $arg_range;

    # Reset the args info.
    @ArgObjects = ();
    @ArgNames = ();
    @ArgTypes = ();
    @ArgFlags = ();
    @ArgNicks = ();
    @ArgBlurbs = ();
    @ArgDefaults = ();
    @ArgRanges = ();

    if (! -f $file) {
        return;
    }
    if (!open (INPUT, $file)) {
        warn "Can't open $file - skipping args\n";
        return;
    }
    while (<INPUT>) {
        if (!$in_arg) {
            if (m/^<ARG>/) {
                $in_arg = 1;
                $arg_object = "";
                $arg_name = "";
                $arg_type = "";
                $arg_flags = "";
                $arg_nick = "";
                $arg_blurb = "";
                $arg_default = "";
                $arg_range = "";
            }
        } else {
            if (m/^<NAME>(.*)<\/NAME>/) {
                $arg_name = $1;
                if ($arg_name =~ m/^(.*)::(.*)$/) {
                    $arg_object = $1;
                    ($arg_name = $2) =~ s/_/-/g;
                    #("Found arg: $arg_name\n");
                } else {
                    &LogWarning ($file, $., "Invalid argument name: $arg_name");
                }
            } elsif (m/^<TYPE>(.*)<\/TYPE>/) {
                $arg_type = $1;
            } elsif (m/^<RANGE>(.*)<\/RANGE>/) {
                $arg_range = $1;
            } elsif (m/^<FLAGS>(.*)<\/FLAGS>/) {
                $arg_flags = $1;
            } elsif (m/^<NICK>(.*)<\/NICK>/) {
                $arg_nick = $1;
            } elsif (m/^<BLURB>(.*)<\/BLURB>/) {
                $arg_blurb = $1;
                if ($arg_blurb eq "(null)") {
                  $arg_blurb = "";
                  &LogWarning ($file, $., "Property ${arg_object}:${arg_name} has no documentation.");
                }
            } elsif (m/^<DEFAULT>(.*)<\/DEFAULT>/) {
                $arg_default = $1;
            } elsif (m%^</ARG>%) {
                #("Found end of arg: ${arg_object}::${arg_name}\n${arg_type} : ${arg_flags}\n");
                push (@ArgObjects, $arg_object);
                push (@ArgNames, $arg_name);
                push (@ArgTypes, $arg_type);
                push (@ArgRanges, $arg_range);
                push (@ArgFlags, $arg_flags);
                push (@ArgNicks, $arg_nick);
                push (@ArgBlurbs, $arg_blurb);
                push (@ArgDefaults, $arg_default);
                $in_arg = 0;
            }
        }
    }
    close (INPUT);
}

#############################################################################
# Function    : AddTreeLineArt
# Description : Add unicode lineart to a pre-indented string array and returns
#               it as as multiline string.
# Arguments   : @tree - array of indented strings.
#############################################################################

sub AddTreeLineArt {
  my @tree = @{$_[0]};
  my $i;
  my $j;
  my $indent;
  
  # iterate bottom up over the tree 
  for ($i = $#tree; $i >= 0; $i--) {
    # count leading spaces
    $tree[$i] =~ /^([^<A-Za-z]*)/;
    $indent = length( $1 );
    # replace with ╰───, if place of ╰ is not space insert ├
    if ($indent > 4) {
      if (substr($tree[$i],$indent-4,1) eq " ") {
        substr($tree[$i],$indent-4,4) = "--- ";
      } else {
        substr($tree[$i],$indent-4,4) = "+-- ";
      }
      # go lines up while space and insert |
      for ($j = $i - 1; ($j >= 0 && substr($tree[$j],$indent-4,1) eq ' '); $j--) {
        substr($tree[$j],$indent-4,1) = '|';
      }
    }
  }
  
  my $res = join("\n", @tree);
  # unicode chars for: ╰──
  $res =~ s%---%<phrase role=\"lineart\">&#9584;&#9472;&#9472;</phrase>%g;
  # unicde chars for: ├──
  $res =~ s%\+--%<phrase role=\"lineart\">&#9500;&#9472;&#9472;</phrase>%g;
  # unicode char for: │
  $res =~ s%\|%<phrase role=\"lineart\">&#9474;</phrase>%g;
  
  return $res;
}


#############################################################################
# Function    : CheckIsObject
# Description : Returns 1 if the given name is a GObject or a subclass.
#                It uses the global @Objects array.
#                Note that the @Objects array only contains classes in the
#                current module and their ancestors - not all GObject classes.
# Arguments   : $name - the name to check.
#############################################################################

sub CheckIsObject {
    my ($name) = @_;
    my $root = $ObjectRoots{$name};
    # Let GBoxed pass as an object here to get -struct appended to the id
    # and prevent conflicts with sections.
    return (defined($root) and $root ne 'GEnum' and $root ne 'GFlags');
}


#############################################################################
# Function    : MakeReturnField
# Description : Pads a string to $RETURN_TYPE_FIELD_WIDTH.
# Arguments   : $str - the string to pad.
#############################################################################

sub MakeReturnField {
    my ($str) = @_;

    return $str . (' ' x ($RETURN_TYPE_FIELD_WIDTH - length ($str)));
}

#############################################################################
# Function    : GetSymbolSourceFile
# Description : Get the filename where the symbol docs where taken from.
# Arguments   : $symbol - the symbol name
#############################################################################

sub GetSymbolSourceFile {
    my ($symbol) = @_;

    if (defined($SourceSymbolSourceFile{$symbol})) {
        return $SourceSymbolSourceFile{$symbol};
    } elsif (defined($SymbolSourceFile{$symbol})) {
        return $SymbolSourceFile{$symbol};
    } else {
        return "";
    }
}

#############################################################################
# Function    : GetSymbolSourceLine
# Description : Get the file line where the symbol docs where taken from.
# Arguments   : $symbol - the symbol name
#############################################################################

sub GetSymbolSourceLine {
    my ($symbol) = @_;

    if (defined($SourceSymbolSourceLine{$symbol})) {
        return $SourceSymbolSourceLine{$symbol};
    } elsif (defined($SymbolSourceLine{$symbol})) {
        return $SymbolSourceLine{$symbol};
    } else {
        return 0;
    }
}

