rebase: add tracing support
[gtk-doc.git] / gtkdoc-mkdb.in
blobab1ab20beee3e2a66b81f29a638caa96a60aa548
1 #!@PERL@ -w
2 # -*- cperl -*-
4 # gtk-doc - GTK DocBook documentation generator.
5 # Copyright (C) 1998  Damon Chaplin
6 #               2007,2008,2009  Stefan Kost
8 # This program is free software; you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation; either version 2 of the License, or
11 # (at your option) any later version.
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 # GNU General Public License for more details.
18 # You should have received a copy of the GNU General Public License
19 # along with this program; if not, write to the Free Software
20 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
23 #############################################################################
24 # Script      : gtkdoc-mkdb
25 # Description : This creates the DocBook files from the edited templates.
26 #############################################################################
28 use warnings;
29 use strict;
30 use Getopt::Long;
32 push @INC, '@PACKAGE_DATA_DIR@';
33 require "gtkdoc-common.pl";
35 # Options
37 # name of documentation module
38 my $MODULE;
39 my $TMPL_DIR;
40 my $DB_OUTPUT_DIR;
41 my @SOURCE_DIRS;
42 my $SOURCE_SUFFIXES = "";
43 my $IGNORE_FILES = "";
44 my $PRINT_VERSION;
45 my $PRINT_HELP;
46 my $MAIN_SGML_FILE;
47 my $EXPAND_CONTENT_FILES = "";
48 my $INLINE_MARKUP_MODE;
49 my $DEFAULT_STABILITY;
50 my $DEFAULT_INCLUDES;
51 my $OUTPUT_FORMAT;
52 my $NAME_SPACE = "";
53 my $OUTPUT_ALL_SYMBOLS;
54 my $OUTPUT_SYMBOLS_WITHOUT_SINCE;
55 my $ROOT_DIR = ".";
56 my $OBJECT_TREE_FILE;
57 my $INTERFACES_FILE;
58 my $PREREQUISITES_FILE;
59 my $SIGNALS_FILE;
60 my $ARGS_FILE;
62 # These global arrays store information on signals. Each signal has an entry
63 # in each of these arrays at the same index, like a multi-dimensional array.
64 my @SignalObjects;        # The GtkObject which emits the signal.
65 my @SignalNames;        # The signal name.
66 my @SignalReturns;        # The return type.
67 my @SignalFlags;        # Flags for the signal
68 my @SignalPrototypes;        # The rest of the prototype of the signal handler.
70 # These global arrays store information on Args. Each Arg has an entry
71 # in each of these arrays at the same index, like a multi-dimensional array.
72 my @ArgObjects;                # The GtkObject which has the Arg.
73 my @ArgNames;                # The Arg name.
74 my @ArgTypes;                # The Arg type - gint, GtkArrowType etc.
75 my @ArgFlags;                # How the Arg can be used - readable/writable etc.
76 my @ArgNicks;                # The nickname of the Arg.
77 my @ArgBlurbs;          # Docstring of the Arg.
78 my @ArgDefaults;        # Default value of the Arg.
79 my @ArgRanges;                # The range of the Arg type
80 # These global hashes store declaration info keyed on a symbol name.
81 my %Declarations;
82 my %DeclarationTypes;
83 my %DeclarationConditional;
84 my %DeclarationOutput;
85 my %Deprecated;
86 my %Since;
87 my %StabilityLevel;
88 my %StructHasTypedef;
90 # These global hashes store the existing documentation.
91 my %SymbolDocs;
92 my %SymbolTypes;
93 my %SymbolParams;
94 my %SymbolSourceFile;
95 my %SymbolSourceLine;
96 my %SymbolAnnotations;
98 # These global hashes store documentation scanned from the source files.
99 my %SourceSymbolDocs;
100 my %SourceSymbolParams;
101 my %SourceSymbolSourceFile;
102 my %SourceSymbolSourceLine;
104 # all documentation goes in here, so we can do coverage analysis
105 my %AllSymbols;
106 my %AllIncompleteSymbols;
107 my %AllUnusedSymbols;
108 my %AllDocumentedSymbols;
110 # Undeclared yet documented symbols
111 my %UndeclaredSymbols;
113 # These global arrays store GObject, subclasses and the hierarchy (also of
114 # non-object derived types).
115 my @Objects;
116 my @ObjectLevels;
117 my %ObjectRoots;
119 my %Interfaces;
120 my %Prerequisites;
122 # holds the symbols which are mentioned in $MODULE-sections.txt and in which
123 # section they are defined
124 my %KnownSymbols;
125 my %SymbolSection;
126 my %SymbolSectionId;
128 # collects index entries
129 my %IndexEntriesFull;
130 my %IndexEntriesSince;
131 my %IndexEntriesDeprecated;
133 # Standard C preprocessor directives, which we ignore for '#' abbreviations.
134 my %PreProcessorDirectives = (
135     'assert' => 1,
136     'define' => 1,
137     'elif' => 1,
138     'else' => 1,
139     'endif' => 1,
140     'error' => 1,
141     'if' => 1,
142     'ifdef' => 1,
143     'ifndef' => 1,
144     'include' => 1,
145     'line' => 1,
146     'pragma' => 1,
147     'unassert' => 1,
148     'undef' => 1,
149     'warning' => 1
152 # remember used annotation (to write minimal glossary)
153 my %AnnotationsUsed;
155 my %AnnotationDefinition = (
156     # the GObjectIntrospection annotations are defined at:
157     # https://live.gnome.org/GObjectIntrospection/Annotations
158     'allow-none' => "NULL is OK, both for passing and for returning.",
159     'nullable' => "NULL may be passed as the value in, out, in-out; or as a return value.",
160     'not nullable' => "NULL must not be passed as the value in, out, in-out; or as a return value.",
161     'optional' => "NULL may be passed instead of a pointer to a location.",
162     'array' => "Parameter points to an array of items.",
163     'attribute' => "Deprecated free-form custom annotation, replaced by (attributes) annotation.",
164     'attributes' => "Free-form key-value pairs.",
165     'closure' => "This parameter is a 'user_data', for callbacks; many bindings can pass NULL here.",
166     'constructor' => "This symbol is a constructor, not a static method.",
167     'destroy' => "This parameter is a 'destroy_data', for callbacks.",
168     'default' => "Default parameter value (for in case the <acronym>shadows</acronym>-to function has less parameters).",
169     'element-type' => "Generics and defining elements of containers and arrays.",
170     'error-domains' => "Typed errors. Similar to throws in Java.",
171     'foreign' => "This is a foreign struct.",
172     'get-value-func' => "The specified function is used to convert a struct from a GValue, must be a GTypeInstance.",
173     'in' => "Parameter for input. Default is <acronym>transfer none</acronym>.",
174     'inout' => "Parameter for input and for returning results. Default is <acronym>transfer full</acronym>.",
175     'in-out' => "Parameter for input and for returning results. Default is <acronym>transfer full</acronym>.",
176     'method' => "This is a method",
177     'not-error' => "A GError parameter is not to be handled like a normal GError.",
178     'out' => "Parameter for returning results. Default is <acronym>transfer full</acronym>.",
179     'out caller-allocates' => "Out parameter, where caller must allocate storage.",
180     'out callee-allocates' => "Out parameter, where caller must allocate storage.",
181     'ref-func' => "The specified function is used to ref a struct, must be a GTypeInstance.",
182     'rename-to' => "Rename the original symbol's name to SYMBOL.",
183     'scope call' => "The callback is valid only during the call to the method.",
184     'scope async' => "The callback is valid until first called.",
185     'scope notified' => "The callback is valid until the GDestroyNotify argument is called.",
186     'set-value-func' => "The specified function is used to convert from a struct to a GValue, must be a GTypeInstance.",
187     'skip' => "Exposed in C code, not necessarily available in other languages.",
188     'transfer container' => "Free data container after the code is done.",
189     'transfer floating' => "Alias for <acronym>transfer none</acronym>, used for objects with floating refs.",
190     'transfer full' => "Free data after the code is done.",
191     'transfer none' => "Don't free data after the code is done.",
192     'type' => "Override the parsed C type with given type.",
193     'unref-func' => "The specified function is used to unref a struct, must be a GTypeInstance.",
194     'virtual' => "This is the invoker for a virtual method.",
195     'value' => "The specified value overrides the evaluated value of the constant.",
196     # Stability Level definition
197     # https://bugzilla.gnome.org/show_bug.cgi?id=170860
198     'Stable' => <<EOF,
199 The intention of a Stable interface is to enable arbitrary third parties to
200 develop applications to these interfaces, release them, and have confidence that
201 they will run on all minor releases of the product (after the one in which the
202 interface was introduced, and within the same major release). Even at a major
203 release, incompatible changes are expected to be rare, and to have strong
204 justifications.
206     'Unstable' => <<EOF,
207 Unstable interfaces are experimental or transitional. They are typically used to
208 give outside developers early access to new or rapidly changing technology, or
209 to provide an interim solution to a problem where a more general solution is
210 anticipated. No claims are made about either source or binary compatibility from
211 one minor release to the next.
213 The Unstable interface level is a warning that these interfaces are  subject to
214 change without warning and should not be used in unbundled products.
216 Given such caveats, customer impact need not be a factor when considering
217 incompatible changes to an Unstable interface in a major or minor release.
218 Nonetheless, when such changes are introduced, the changes should still be
219 mentioned in the release notes for the affected release.
221     'Private' => <<EOF
222 An interface that can be used within the GNOME stack itself, but that is not
223 documented for end-users.  Such functions should only be used in specified and
224 documented ways.
228 # Elements to consider non-block items in MarkDown parsing
229 my %MD_TEXT_LEVEL_ELEMENTS = ( "literal" => 1,
230                                "emphasis" => 1,
231                                "envar" => 1,
232                                "filename" => 1,
233                                "firstterm" => 1,
234                                "footnote" => 1,
235                                "function" => 1,
236                                "manvolnum" => 1,
237                                "option" => 1,
238                                "replaceable" => 1,
239                                "structfield" => 1,
240                                "structname" => 1,
241                                "title" => 1,
242                                "varname" => 1 );
243 my %MD_ESCAPABLE_CHARS = ( "\\" => 1,
244                            "`" => 1,
245                            "*" => 1,
246                            "_" => 1,
247                            "{" => 1,
248                            "}" => 1,
249                            "[" => 1,
250                            "]" => 1,
251                            "(" => 1,
252                            ")" => 1,
253                            ">" => 1,
254                            "#" => 1,
255                            "+" => 1,
256                            "-" => 1,
257                            "." => 1,
258                            "!" => 1 );
259 my %MD_GTK_ESCAPABLE_CHARS = ( "@" => 1,
260                                "%" => 1 );
262 # Function and other declaration output settings.
263 my $RETURN_TYPE_FIELD_WIDTH = 20;
264 my $SYMBOL_FIELD_WIDTH = 36;
265 my $MAX_SYMBOL_FIELD_WIDTH = 40;
266 my $SIGNAL_FIELD_WIDTH = 16;
267 my $PARAM_FIELD_COUNT = 2;
269 # XML, SGML formatting helper
270 my $doctype_header;
273 run() unless caller; # Run program unless loaded as a module
276 sub run {
277     my %optctl = ('module' => \$MODULE,
278                   'source-dir' => \@SOURCE_DIRS,
279                   'source-suffixes' => \$SOURCE_SUFFIXES,
280                   'ignore-files' => \$IGNORE_FILES,
281                   'output-dir' => \$DB_OUTPUT_DIR,
282                   'tmpl-dir' => \$TMPL_DIR,
283                   'version' => \$PRINT_VERSION,
284                   'help' => \$PRINT_HELP,
285                   'main-sgml-file' => \$MAIN_SGML_FILE,
286                   'expand-content-files' => \$EXPAND_CONTENT_FILES,
287                   'sgml-mode' => \$INLINE_MARKUP_MODE,
288                   'xml-mode' => \$INLINE_MARKUP_MODE,
289                   'default-stability' => \$DEFAULT_STABILITY,
290                   'default-includes' => \$DEFAULT_INCLUDES,
291                   'output-format' => \$OUTPUT_FORMAT,
292                   'name-space' => \$NAME_SPACE,
293                   'outputallsymbols' => \$OUTPUT_ALL_SYMBOLS,
294                   'outputsymbolswithoutsince' => \$OUTPUT_SYMBOLS_WITHOUT_SINCE
295                   );
296     GetOptions(\%optctl, "module=s", "source-dir:s", "source-suffixes:s",
297         "ignore-files:s", "output-dir:s", "tmpl-dir:s", "version",
298         "outputallsymbols", "outputsymbolswithoutsince",
299         "expand-content-files:s", "main-sgml-file:s", "extra-db-files:s", "help",
300         "sgml-mode", "xml-mode", "default-stability:s", "default-includes:s",
301         "output-format:s", "name-space:s");
303     if ($PRINT_VERSION) {
304         print "@VERSION@\n";
305         exit 0;
306     }
308     if (!$MODULE) {
309         $PRINT_HELP = 1;
310     }
312     if ($DEFAULT_STABILITY && $DEFAULT_STABILITY ne "Stable"
313         && $DEFAULT_STABILITY ne "Private" && $DEFAULT_STABILITY ne "Unstable") {
314         $PRINT_HELP = 1;
315     }
317     if ($PRINT_HELP) {
318         print <<EOF;
319 gtkdoc-mkdb version @VERSION@ - generate docbook files
321 --module=MODULE_NAME       Name of the doc module being parsed
322 --source-dir=DIRNAME       Directories which contain inline reference material
323 --source-suffixes=SUFFIXES Suffixes of source files to scan, comma-separated
324 --ignore-files=FILES       A space-separated list of header files/dirs not to
325                            scan
326 --output-dir=DIRNAME       Directory to put the generated DocBook files in
327 --tmpl-dir=DIRNAME         Directory in which template files may be found
328 --main-sgml-file=FILE      File containing the toplevel DocBook file.
329 --expand-content-files=FILES Extra DocBook files to expand abbreviations in.
330 --output-format=FORMAT     Format to use for the generated docbook, XML or SGML.
331 --{xml,sgml}-mode          Allow DocBook markup in inline documentation.
332 --default-stability=LEVEL  Specify default stability Level. Valid values are
333                            Stable, Unstable, or Private.
334 --default-includes=FILENAMES Specify default includes for section Synopsis
335 --name-space=NS            Omit namespace in index.
336 --version                  Print the version of this program
337 --help                     Print this help
339         exit 0;
340     }
342     @TRACE@(" ignore files: [$IGNORE_FILES]\n");
344     # check output format
345     if (! defined($OUTPUT_FORMAT) || ($OUTPUT_FORMAT eq "")) {
346         $OUTPUT_FORMAT = "xml";
347     } else {
348         $OUTPUT_FORMAT = lc($OUTPUT_FORMAT);
349     }
350     if ($OUTPUT_FORMAT ne "xml") {
351         die "Invalid format '$OUTPUT_FORMAT' passed to --output.format"
352     }
354     if (!$MAIN_SGML_FILE) {
355         # backwards compatibility
356         if (-e "${MODULE}-docs.sgml") {
357             $MAIN_SGML_FILE = "${MODULE}-docs.sgml";
358         } else {
359             $MAIN_SGML_FILE = "${MODULE}-docs.xml";
360         }
361     }
363     # extract docbook header or define default
364     if (-e $MAIN_SGML_FILE) {
365         open(INPUT, "<$MAIN_SGML_FILE") || die "Can't open $MAIN_SGML_FILE";
366         $doctype_header = "";
367         while (<INPUT>) {
368             if (/^\s*<(book|chapter|article)/) {
369                 # check that the top-level tagSYSTEM or the doctype decl contain the xinclude namespace decl
370                 if (($_ !~ m/http:\/\/www.w3.org\/200[13]\/XInclude/) && ($doctype_header !~ m/http:\/\/www.w3.org\/200[13]\/XInclude/m)) {
371                     $doctype_header = "";
372                 }
373                 last;
374             }
375             # if there are SYSTEM ENTITIES here, we should prepend "../" to the path
376             # FIXME: not sure if we can do this now, as people already work-around the problem
377             # s#<!ENTITY % ([a-zA-Z-]+) SYSTEM \"([^/][a-zA-Z./]+)\">#<!ENTITY % $1 SYSTEM \"../$2\">#;
378             s#<!ENTITY % gtkdocentities SYSTEM \"([^"]*)\">#<!ENTITY % gtkdocentities SYSTEM \"../$1\">#;
379             $doctype_header .= $_;
380         }
381         close(INPUT);
382     } else {
383         $doctype_header = <<EOF;
384 <?xml version="1.0"?>
385 <!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.3//EN"
386                "http://www.oasis-open.org/docbook/xml/4.3/docbookx.dtd"
388   <!ENTITY % local.common.attrib "xmlns:xi  CDATA  #FIXED 'http://www.w3.org/2003/XInclude'">
389   <!ENTITY % gtkdocentities SYSTEM "../xml/gtkdocentities.ent">
390   %gtkdocentities;
393     }
394     chomp($doctype_header);
396     # All the files are written in subdirectories beneath here.
397     $TMPL_DIR = $TMPL_DIR ? $TMPL_DIR : "$ROOT_DIR/tmpl";
399     # This is where we put all the DocBook output.
400     $DB_OUTPUT_DIR = $DB_OUTPUT_DIR ? $DB_OUTPUT_DIR : "$ROOT_DIR/xml";
402     # This file contains the object hierarchy.
403     $OBJECT_TREE_FILE = "$ROOT_DIR/$MODULE.hierarchy";
405     # This file contains the interfaces.
406     $INTERFACES_FILE = "$ROOT_DIR/$MODULE.interfaces";
408     # This file contains the prerequisites.
409     $PREREQUISITES_FILE = "$ROOT_DIR/$MODULE.prerequisites";
411     # This file contains signal arguments and names.
412     $SIGNALS_FILE = "$ROOT_DIR/$MODULE.signals";
414     # The file containing Arg information.
415     $ARGS_FILE = "$ROOT_DIR/$MODULE.args";
417     # Create the root DocBook output directory if it doens't exist.
418     if (! -e $DB_OUTPUT_DIR) {
419         mkdir ("$DB_OUTPUT_DIR", 0777)
420             || die "Can't create directory: $DB_OUTPUT_DIR";
421     }
423     &ReadKnownSymbols ("$ROOT_DIR/$MODULE-sections.txt");
424     &ReadSignalsFile ($SIGNALS_FILE);
425     &ReadArgsFile ($ARGS_FILE);
426     &ReadObjectHierarchy;
427     &ReadInterfaces;
428     &ReadPrerequisites;
430     &ReadDeclarationsFile ("$ROOT_DIR/$MODULE-decl.txt", 0);
431     if (-f "$ROOT_DIR/$MODULE-overrides.txt") {
432         &ReadDeclarationsFile ("$ROOT_DIR/$MODULE-overrides.txt", 1);
433     }
435     for my $dir (@SOURCE_DIRS) {
436         &ReadSourceDocumentation ($dir);
437     }
439     my $changed = &OutputDB ("$ROOT_DIR/$MODULE-sections.txt");
441     # If any of the DocBook files have changed, update the timestamp file (so
442     # it can be used for Makefile dependencies).
443     if ($changed || ! -e "$ROOT_DIR/sgml.stamp") {
445         # try to detect the common prefix
446         # GtkWidget, GTK_WIDGET, gtk_widget -> gtk
447         if ($NAME_SPACE eq "") {
448             $NAME_SPACE="";
449             my $pos=0;
450             my $ratio=0.0;
451             do {
452                 my %prefix;
453                 my $letter="";
454                 foreach my $symbol (keys(%IndexEntriesFull)) {
455                     if(($NAME_SPACE eq "") || $symbol =~ /^$NAME_SPACE/i) {
456                         if (length($symbol)>$pos) {
457                             $letter=substr($symbol,$pos,1);
458                             # stop prefix scanning
459                             if ($letter eq "_") {
460                                 # stop on "_"
461                                 last;
462                             }
463                             # Should we also stop on a uppercase char, if last was lowercase
464                             #   GtkWidget, if we have the 'W' and had the 't' before
465                             # or should we count upper and lowercase, and stop one 2nd uppercase, if we already had a lowercase
466                             #   GtkWidget, the 'W' would be the 2nd uppercase and with 't','k' we had lowercase chars before
467                             # need to recound each time as this is per symbol
468                             $prefix{uc($letter)}++;
469                         }
470                     }
471                 }
472                 if ($letter ne "" && $letter ne "_") {
473                     my $maxletter="";
474                     my $maxsymbols=0;
475                     foreach $letter (keys(%prefix)) {
476                         #print "$letter: $prefix{$letter}.\n";
477                         if ($prefix{$letter}>$maxsymbols) {
478                             $maxletter=$letter;
479                             $maxsymbols=$prefix{$letter};
480                         }
481                     }
482                     $ratio = scalar(keys(%IndexEntriesFull)) / $prefix{$maxletter};
483                     #print "most symbols start with $maxletter, that is ". (100 * $ratio) ." %\n";
484                     if ($ratio > 0.9) {
485                         # do another round
486                         $NAME_SPACE .= $maxletter;
487                     }
488                     $pos++;
489                 }
490                 else {
491                     $ratio=0.0;
492                 }
493             } while ($ratio > 0.9);
494             #print "most symbols start with $NAME_SPACE\n";
495         }
497         &OutputIndexFull;
498         &OutputDeprecatedIndex;
499         &OutputSinceIndexes;
500         &OutputAnnotationGlossary;
502         open (TIMESTAMP, ">$ROOT_DIR/sgml.stamp")
503             || die "Can't create $ROOT_DIR/sgml.stamp: $!";
504         print (TIMESTAMP "timestamp");
505         close (TIMESTAMP);
506     }
509 #############################################################################
510 # Function    : OutputObjectList
511 # Description : This outputs the alphabetical list of objects, in a columned
512 #                table.
513 #               FIXME: Currently this also outputs ancestor objects
514 #                which may not actually be in this module.
515 # Arguments   : none
516 #############################################################################
518 sub OutputObjectList {
519     my $cols = 3;
521     # FIXME: use .xml
522     # my $old_object_index = "$DB_OUTPUT_DIR/object_index.xml";
523     my $old_object_index = "$DB_OUTPUT_DIR/object_index.sgml";
524     my $new_object_index = "$DB_OUTPUT_DIR/object_index.new";
526     open (OUTPUT, ">$new_object_index")
527         || die "Can't create $new_object_index: $!";
529     print (OUTPUT <<EOF);
530 ${\( MakeDocHeader ("informaltable") )}
531 <informaltable pgwide="1" frame="none">
532 <tgroup cols="$cols">
533 <colspec colwidth="1*"/>
534 <colspec colwidth="1*"/>
535 <colspec colwidth="1*"/>
536 <tbody>
539     my $count = 0;
540     my $object;
541     foreach $object (sort (@Objects)) {
542         my $xref = &MakeXRef ($object);
543         if ($count % $cols == 0) { print (OUTPUT "<row>\n"); }
544         print (OUTPUT "<entry>$xref</entry>\n");
545         if ($count % $cols == ($cols - 1)) { print (OUTPUT "</row>\n"); }
546         $count++;
547     }
548     if ($count == 0) {
549         # emit an empty row, since empty tables are invalid
550         print (OUTPUT "<row><entry> </entry></row>\n");
551     }
552     else {
553         if ($count % $cols > 0) {
554             print (OUTPUT "</row>\n");
555         }
556     }
558     print (OUTPUT <<EOF);
559 </tbody></tgroup></informaltable>
561     close (OUTPUT);
563     &UpdateFileIfChanged ($old_object_index, $new_object_index, 0);
566 #############################################################################
567 # Function    : TrimTextBlock
568 # Description : Trims extra whitespace. Empty lines inside a block are
569 #                preserved.
570 # Arguments   : $desc - the text block to trim. May contain newlines.
571 #############################################################################
573 sub TrimTextBlock {
574   my ($desc) = @_;
576   # strip leading spaces on the block
577   $desc =~ s/^\s+//s;
578   # strip trailing spaces on every line
579   $desc =~ s/\s+$/\n/mg;
581   return $desc;
585 #############################################################################
586 # Function    : OutputDB
587 # Description : This collects the output for each section of the docs, and
588 #                outputs each file when the end of the section is found.
589 # Arguments   : $file - the $MODULE-sections.txt file which contains all of
590 #                the functions/macros/structs etc. being documented, organised
591 #                into sections and subsections.
592 #############################################################################
594 sub OutputDB {
595     my ($file) = @_;
597     @TRACE@("Reading: $file\n");
598     open (INPUT, $file)
599         || die "Can't open $file: $!";
600     my $filename = "";
601     my $book_top = "";
602     my $book_bottom = "";
603     my $includes = (defined $DEFAULT_INCLUDES) ? $DEFAULT_INCLUDES : "";
604     my $section_includes = "";
605     my $in_section = 0;
606     my $title = "";
607     my $section_id = "";
608     my $subsection = "";
609     my $num_symbols;
610     my $changed = 0;
611     my $functions_synop = "";
612     my $other_synop = "";
613     my $functions_details = "";
614     my $other_details = "";
615     my $signals_synop = "";
616     my $signals_desc = "";
617     my $args_synop = "";
618     my $child_args_synop = "";
619     my $style_args_synop = "";
620     my $args_desc = "";
621     my $child_args_desc = "";
622     my $style_args_desc = "";
623     my $hierarchy_str = "";
624     my @hierarchy = ();
625     my $interfaces = "";
626     my $implementations = "";
627     my $prerequisites = "";
628     my $derived = "";
629     my @file_objects = ();
630     my %templates = ();
631     my %symbol_def_line = ();
633     # merge the source docs, in case there are no templates
634     &MergeSourceDocumentation;
636     while (<INPUT>) {
637         if (m/^#/) {
638             next;
640         } elsif (m/^<SECTION>/) {
641             $num_symbols = 0;
642             $in_section = 1;
643             @file_objects = ();
644             %symbol_def_line = ();
646         } elsif (m/^<SUBSECTION\s*(.*)>/i) {
647             $other_synop .= "\n";
648             $functions_synop .= "\n";
649             $subsection = $1;
651         } elsif (m/^<SUBSECTION>/) {
653         } elsif (m/^<TITLE>(.*)<\/TITLE>/) {
654             $title = $1;
655             @TRACE@("Section: $title\n");
657             # We don't want warnings if object & class structs aren't used.
658             $DeclarationOutput{$title} = 1;
659             $DeclarationOutput{"${title}Class"} = 1;
660             $DeclarationOutput{"${title}Iface"} = 1;
661             $DeclarationOutput{"${title}Interface"} = 1;
663         } elsif (m/^<FILE>(.*)<\/FILE>/) {
664             $filename = $1;
665             if (! defined $templates{$filename}) {
666                if (&ReadTemplateFile ("$TMPL_DIR/$filename", 1)) {
667                    &MergeSourceDocumentation;
668                    $templates{$filename}=$.;
669                }
670             } else {
671                 &LogWarning ($file, $., "Double <FILE>$filename</FILE> entry. ".
672                     "Previous occurrence on line ".$templates{$filename}.".");
673             }
674             if (($title eq "") and (defined $SourceSymbolDocs{"$TMPL_DIR/$filename:Title"})) {
675                 $title = $SourceSymbolDocs{"$TMPL_DIR/$filename:Title"};
676                  # Remove trailing blanks
677                 $title =~ s/\s+$//;
678            }
680         } elsif (m/^<INCLUDE>(.*)<\/INCLUDE>/) {
681             if ($in_section) {
682                 $section_includes = $1;
683             } else {
684                 if (defined $DEFAULT_INCLUDES) {
685                     &LogWarning ($file, $., "Default <INCLUDE> being overridden by command line option.");
686                 }
687                 else {
688                     $includes = $1;
689                 }
690             }
692         } elsif (m/^<\/SECTION>/) {
693             @TRACE@("End of section: $title\n");
694             if ($num_symbols > 0) {
695                 # collect documents
696                 $book_bottom .= "    <xi:include href=\"xml/$filename.xml\"/>\n";
698                 if (defined ($SourceSymbolDocs{"$TMPL_DIR/$filename:Include"})) {
699                     if ($section_includes) {
700                         &LogWarning ($file, $., "Section <INCLUDE> being overridden by inline comments.");
701                     }
702                     $section_includes = $SourceSymbolDocs{"$TMPL_DIR/$filename:Include"};
703                 }
704                 if ($section_includes eq "") {
705                     $section_includes = $includes;
706                 }
708                  $signals_synop =~ s/^\n*//g;
709                  $signals_synop =~ s/\n+$/\n/g;
710                 if ($signals_synop ne '') {
711                     $signals_synop = <<EOF;
712 <refsect1 id="$section_id.signals" role="signal_proto">
713 <title role="signal_proto.title">Signals</title>
714 <informaltable frame="none">
715 <tgroup cols="3">
716 <colspec colname="signals_return" colwidth="150px"/>
717 <colspec colname="signals_name" colwidth="300px"/>
718 <colspec colname="signals_flags" colwidth="200px"/>
719 <tbody>
720 ${signals_synop}
721 </tbody>
722 </tgroup>
723 </informaltable>
724 </refsect1>
726                      $signals_desc = TrimTextBlock($signals_desc);
727                     $signals_desc  = <<EOF;
728 <refsect1 id="$section_id.signal-details" role="signals">
729 <title role="signals.title">Signal Details</title>
730 $signals_desc
731 </refsect1>
733                 }
735                 $args_synop =~ s/^\n*//g;
736                 $args_synop =~ s/\n+$/\n/g;
737                 if ($args_synop ne '') {
738                     $args_synop = <<EOF;
739 <refsect1 id="$section_id.properties" role="properties">
740 <title role="properties.title">Properties</title>
741 <informaltable frame="none">
742 <tgroup cols="3">
743 <colspec colname="properties_type" colwidth="150px"/>
744 <colspec colname="properties_name" colwidth="300px"/>
745 <colspec colname="properties_flags" colwidth="200px"/>
746 <tbody>
747 ${args_synop}
748 </tbody>
749 </tgroup>
750 </informaltable>
751 </refsect1>
753                      $args_desc = TrimTextBlock($args_desc);
754                     $args_desc  = <<EOF;
755 <refsect1 id="$section_id.property-details" role="property_details">
756 <title role="property_details.title">Property Details</title>
757 $args_desc
758 </refsect1>
760                 }
762                 $child_args_synop =~ s/^\n*//g;
763                 $child_args_synop =~ s/\n+$/\n/g;
764                 if ($child_args_synop ne '') {
765                     $args_synop .= <<EOF;
766 <refsect1 id="$section_id.child-properties" role="child_properties">
767 <title role="child_properties.title">Child Properties</title>
768 <informaltable frame="none">
769 <tgroup cols="3">
770 <colspec colname="child_properties_type" colwidth="150px"/>
771 <colspec colname="child_properties_name" colwidth="300px"/>
772 <colspec colname="child_properties_flags" colwidth="200px"/>
773 <tbody>
774 ${child_args_synop}
775 </tbody>
776 </tgroup>
777 </informaltable>
778 </refsect1>
780                      $child_args_desc = TrimTextBlock($child_args_desc);
781                      $args_desc .= <<EOF;
782 <refsect1 id="$section_id.child-property-details" role="child_property_details">
783 <title role="child_property_details.title">Child Property Details</title>
784 $child_args_desc
785 </refsect1>
787                 }
789                 $style_args_synop =~ s/^\n*//g;
790                 $style_args_synop =~ s/\n+$/\n/g;
791                 if ($style_args_synop ne '') {
792                     $args_synop .= <<EOF;
793 <refsect1 id="$section_id.style-properties" role="style_properties">
794 <title role="style_properties.title">Style Properties</title>
795 <informaltable frame="none">
796 <tgroup cols="3">
797 <colspec colname="style_properties_type" colwidth="150px"/>
798 <colspec colname="style_properties_name" colwidth="300px"/>
799 <colspec colname="style_properties_flags" colwidth="200px"/>
800 <tbody>
801 ${style_args_synop}
802 </tbody>
803 </tgroup>
804 </informaltable>
805 </refsect1>
807                      $style_args_desc = TrimTextBlock($style_args_desc);
808                     $args_desc .= <<EOF;
809 <refsect1 id="$section_id.style-property-details" role="style_properties_details">
810 <title role="style_properties_details.title">Style Property Details</title>
811 $style_args_desc
812 </refsect1>
814                 }
816                 $hierarchy_str = &AddTreeLineArt(\@hierarchy);
817                 if ($hierarchy_str ne "") {
818                     $hierarchy_str = <<EOF;
819 <refsect1 id="$section_id.object-hierarchy" role="object_hierarchy">
820 <title role="object_hierarchy.title">Object Hierarchy</title>
821 <screen>$hierarchy_str
822 </screen>
823 </refsect1>
825                 }
827                  $interfaces =~ TrimTextBlock($interfaces);
828                 if ($interfaces ne "") {
829                     $interfaces = <<EOF;
830 <refsect1 id="$section_id.implemented-interfaces" role="impl_interfaces">
831 <title role="impl_interfaces.title">Implemented Interfaces</title>
832 $interfaces
833 </refsect1>
835                 }
837                  $implementations = TrimTextBlock($implementations);
838                 if ($implementations ne "") {
839                     $implementations = <<EOF;
840 <refsect1 id="$section_id.implementations" role="implementations">
841 <title role="implementations.title">Known Implementations</title>
842 $implementations
843 </refsect1>
845                 }
847                  $prerequisites = TrimTextBlock($prerequisites);
848                 if ($prerequisites ne "") {
849                     $prerequisites = <<EOF;
850 <refsect1 id="$section_id.prerequisites" role="prerequisites">
851 <title role="prerequisites.title">Prerequisites</title>
852 $prerequisites
853 </refsect1>
855                 }
857                  $derived = TrimTextBlock($derived);
858                 if ($derived ne "") {
859                     $derived = <<EOF;
860 <refsect1 id="$section_id.derived-interfaces" role="derived_interfaces">
861 <title role="derived_interfaces.title">Known Derived Interfaces</title>
862 $derived
863 </refsect1>
865                 }
867                 $functions_synop =~ s/^\n*//g;
868                 $functions_synop =~ s/\n+$/\n/g;
869                 if ($functions_synop ne '') {
870                   $functions_synop = <<EOF;
871 <refsect1 id="$section_id.functions" role="functions_proto">
872 <title role="functions_proto.title">Functions</title>
873 <informaltable pgwide="1" frame="none">
874 <tgroup cols="2">
875 <colspec colname="functions_return" colwidth="150px"/>
876 <colspec colname="functions_name"/>
877 <tbody>
878 ${functions_synop}
879 </tbody>
880 </tgroup>
881 </informaltable>
882 </refsect1>
884                 }
886                 $other_synop =~ s/^\n*//g;
887                 $other_synop =~ s/\n+$/\n/g;
888                 if ($other_synop ne '') {
889                   $other_synop = <<EOF;
890 <refsect1 id="$section_id.other" role="other_proto">
891 <title role="other_proto.title">Types and Values</title>
892 <informaltable role="enum_members_table" pgwide="1" frame="none">
893 <tgroup cols="2">
894 <colspec colname="name" colwidth="150px"/>
895 <colspec colname="description"/>
896 <tbody>
897 ${other_synop}
898 </tbody>
899 </tgroup>
900 </informaltable>
901 </refsect1>
903                 }
905                 my $file_changed = &OutputDBFile ($filename, $title, $section_id,
906                                                     $section_includes,
907                                                     \$functions_synop, \$other_synop,
908                                                     \$functions_details, \$other_details,
909                                                     \$signals_synop, \$signals_desc,
910                                                     \$args_synop, \$args_desc,
911                                                     \$hierarchy_str, \$interfaces,
912                                                     \$implementations,
913                                                     \$prerequisites, \$derived,
914                                                     \@file_objects);
915                 if ($file_changed) {
916                     $changed = 1;
917                 }
918             }
919             $title = "";
920             $section_id = "";
921             $subsection = "";
922             $in_section = 0;
923             $section_includes = "";
924             $functions_synop = "";
925             $other_synop = "";
926             $functions_details = "";
927             $other_details = "";
928             $signals_synop = "";
929             $signals_desc = "";
930             $args_synop = "";
931             $child_args_synop = "";
932             $style_args_synop = "";
933             $args_desc = "";
934             $child_args_desc = "";
935             $style_args_desc = "";
936             $hierarchy_str = "";
937             @hierarchy = ();
938             $interfaces = "";
939             $implementations = "";
940             $prerequisites = "";
941             $derived = "";
943         } elsif (m/^(\S+)/) {
944             my $symbol = $1;
945             @TRACE@("  Symbol: $symbol in subsection: $subsection\n");
947             # check for duplicate entries
948             if (! defined $symbol_def_line{$symbol}) {
949                 my $declaration = $Declarations{$symbol};
950                 if (defined ($declaration)) {
951                     if (&CheckIsObject ($symbol)) {
952                         push @file_objects, $symbol;
953                     }
954                     # We don't want standard macros/functions of GObjects,
955                     # or private declarations.
956                     if ($subsection ne "Standard" && $subsection ne "Private") {
957                         my ($synop, $desc) = &OutputDeclaration ($symbol,
958                                                                  $declaration);
959                         my $type = $DeclarationTypes {$symbol};
961                         if ($type eq 'FUNCTION' || $type eq 'USER_FUNCTION') {
962                           $functions_synop .= $synop;
963                           $functions_details .= $desc;
964                         } elsif ($type eq 'MACRO' && $declaration =~ /$symbol\(/) {
965                           $functions_synop .= $synop;
966                           $functions_details .= $desc;
967                         } else {
968                           $other_synop .= $synop;
969                           $other_details .= $desc;
970                         }
971                     }
972                     my ($sig_synop, $sig_desc) = &GetSignals ($symbol);
973                     my ($arg_synop, $child_arg_synop, $style_arg_synop,
974                         $arg_desc, $child_arg_desc, $style_arg_desc) = &GetArgs ($symbol);
975                     my $ifaces = &GetInterfaces ($symbol);
976                     my $impls = &GetImplementations ($symbol);
977                     my $prereqs = &GetPrerequisites ($symbol);
978                     my $der = &GetDerived ($symbol);
979                     @hierarchy = &GetHierarchy ($symbol, \@hierarchy);
981                     $signals_synop .= $sig_synop;
982                     $signals_desc .= $sig_desc;
983                     $args_synop .= $arg_synop;
984                     $child_args_synop .= $child_arg_synop;
985                     $style_args_synop .= $style_arg_synop;
986                     $args_desc .= $arg_desc;
987                     $child_args_desc .= $child_arg_desc;
988                     $style_args_desc .= $style_arg_desc;
989                     $interfaces .= $ifaces;
990                     $implementations .= $impls;
991                     $prerequisites .= $prereqs;
992                     $derived .= $der;
994                     # Note that the declaration has been output.
995                     $DeclarationOutput{$symbol} = 1;
996                 } elsif ($subsection ne "Standard" && $subsection ne "Private") {
997                     $UndeclaredSymbols{$symbol} = 1;
998                     &LogWarning ($file, $., "No declaration found for $symbol.");
999                 }
1000                 $num_symbols++;
1001                 $symbol_def_line{$symbol}=$.;
1003                 if ($section_id eq "") {
1004                     if($title eq "" && $filename eq "") {
1005                         &LogWarning ($file, $., "Section has no title and no file.");
1006                     }
1007                     # FIXME: one of those would be enough
1008                     # filename should be an internal detail for gtk-doc
1009                     if ($title eq "") {
1010                         $title = $filename;
1011                     } elsif ($filename eq "") {
1012                         $filename = $title;
1013                     }
1014                     $filename =~ s/\s/_/g;
1016                     $section_id = $SourceSymbolDocs{"$TMPL_DIR/$filename:Section_Id"};
1017                     if (defined ($section_id) && $section_id !~ m/^\s*$/) {
1018                         # Remove trailing blanks and use as is
1019                         $section_id =~ s/\s+$//;
1020                     } elsif (&CheckIsObject ($title)) {
1021                         # GObjects use their class name as the ID.
1022                         $section_id = &CreateValidSGMLID ($title);
1023                     } else {
1024                         $section_id = &CreateValidSGMLID ("$MODULE-$title");
1025                     }
1026                 }
1027                 $SymbolSection{$symbol}=$title;
1028                 $SymbolSectionId{$symbol}=$section_id;
1029             }
1030             else {
1031                 &LogWarning ($file, $., "Double symbol entry for $symbol. ".
1032                     "Previous occurrence on line ".$symbol_def_line{$symbol}.".");
1033             }
1034         }
1035     }
1036     close (INPUT);
1038     &OutputMissingDocumentation;
1039     &OutputUndeclaredSymbols;
1040     &OutputUnusedSymbols;
1042     if ($OUTPUT_ALL_SYMBOLS) {
1043         &OutputAllSymbols;
1044     }
1045     if ($OUTPUT_SYMBOLS_WITHOUT_SINCE) {
1046         &OutputSymbolsWithoutSince;
1047     }
1049     for $filename (split (' ', $EXPAND_CONTENT_FILES)) {
1050         my $file_changed = &OutputExtraFile ($filename);
1051         if ($file_changed) {
1052             $changed = 1;
1053         }
1054     }
1056     &OutputBook ($book_top, $book_bottom);
1058     return $changed;
1061 #############################################################################
1062 # Function    : OutputIndex
1063 # Description : This writes an indexlist that can be included into the main-
1064 #               document into an <index> tag.
1065 #############################################################################
1067 sub OutputIndex {
1068     my ($basename, $apiindexref ) = @_;
1069     my %apiindex = %{$apiindexref};
1070     my $old_index = "$DB_OUTPUT_DIR/$basename.xml";
1071     my $new_index = "$DB_OUTPUT_DIR/$basename.new";
1072     my $lastletter = " ";
1073     my $divopen = 0;
1074     my $symbol;
1075     my $short_symbol;
1077     open (OUTPUT, ">$new_index")
1078         || die "Can't create $new_index";
1080     print (OUTPUT &MakeDocHeader ("indexdiv")."\n<indexdiv id=\"$basename\">\n");
1082     @TRACE@("generate $basename index (".%apiindex." entries)\n");
1084     # do a case insensitive sort while chopping off the prefix
1085     foreach my $hash (
1086         sort { $$a{criteria} cmp $$b{criteria} or $$a{original} cmp $$b{original} }
1087         map { my $x = uc($_); $x =~ s/^$NAME_SPACE\_?(.*)/$1/i; { criteria => $x, original => $_, short => $1 } }
1088         keys %apiindex) {
1090         $symbol = $$hash{original};
1091         if (defined($$hash{short}) && $$hash{short} ne "") {
1092             $short_symbol = $$hash{short};
1093         } else {
1094             $short_symbol = $symbol;
1095         }
1097         # generate a short symbol description
1098         my $symbol_desc = "";
1099         my $symbol_section = "";
1100         my $symbol_section_id = "";
1101         my $symbol_type = "";
1102         if (defined($DeclarationTypes{$symbol})) {
1103           $symbol_type = lc($DeclarationTypes{$symbol});
1104         }
1105         if ($symbol_type eq "") {
1106             @TRACE@("trying symbol $symbol\n");
1107             if ($symbol =~ m/(.*)::(.*)/) {
1108                 my $oname = $1;
1109                 my $osym = $2;
1110                 my $i;
1111                 @TRACE@("  trying object signal ${oname}:$osym in ".$#SignalNames." signals\n");
1112                 for ($i = 0; $i <= $#SignalNames; $i++) {
1113                     if ($SignalNames[$i] eq $osym) {
1114                         $symbol_type = "object signal";
1115                         if (defined($SymbolSection{$oname})) {
1116                            $symbol_section = $SymbolSection{$oname};
1117                            $symbol_section_id = $SymbolSectionId{$oname};
1118                         }
1119                         last;
1120                     }
1121                 }
1122             } elsif ($symbol =~ m/(.*):(.*)/) {
1123                 my $oname = $1;
1124                 my $osym = $2;
1125                 my $i;
1126                 @TRACE@("  trying object property ${oname}::$osym in ".$#ArgNames." properties\n");
1127                 for ($i = 0; $i <= $#ArgNames; $i++) {
1128                     @TRACE@("    ".$ArgNames[$i]."\n");
1129                     if ($ArgNames[$i] eq $osym) {
1130                         $symbol_type = "object property";
1131                         if (defined($SymbolSection{$oname})) {
1132                            $symbol_section = $SymbolSection{$oname};
1133                            $symbol_section_id = $SymbolSectionId{$oname};
1134                         }
1135                         last;
1136                     }
1137                 }
1138             }
1139         } else {
1140            if (defined($SymbolSection{$symbol})) {
1141                $symbol_section = $SymbolSection{$symbol};
1142                $symbol_section_id = $SymbolSectionId{$symbol};
1143            }
1144         }
1145         if ($symbol_type ne "") {
1146            $symbol_desc=", $symbol_type";
1147            if ($symbol_section ne "") {
1148                $symbol_desc.=" in <link linkend=\"$symbol_section_id\">$symbol_section</link>";
1149                #$symbol_desc.=" in ". &ExpandAbbreviations($symbol, "#$symbol_section");
1150            }
1151         }
1153         my $curletter = uc(substr($short_symbol,0,1));
1154         my $id = $apiindex{$symbol};
1156         @TRACE@("  add symbol $symbol with $id to index in section '$curletter' (derived from $short_symbol)\n");
1158         if ($curletter ne $lastletter) {
1159             $lastletter = $curletter;
1161             if ($divopen == 1) {
1162                 print (OUTPUT "</indexdiv>\n");
1163             }
1164             print (OUTPUT "<indexdiv><title>$curletter</title>\n");
1165             $divopen = 1;
1166         }
1168         print (OUTPUT <<EOF);
1169 <indexentry><primaryie linkends="$id"><link linkend="$id">$symbol</link>$symbol_desc</primaryie></indexentry>
1171     }
1173     if ($divopen == 1) {
1174         print (OUTPUT "</indexdiv>\n");
1175     }
1176     print (OUTPUT "</indexdiv>\n");
1177     close (OUTPUT);
1179     &UpdateFileIfChanged ($old_index, $new_index, 0);
1183 #############################################################################
1184 # Function    : OutputIndexFull
1185 # Description : This writes the full api indexlist that can be included into the
1186 #               main document into an <index> tag.
1187 #############################################################################
1189 sub OutputIndexFull {
1190     &OutputIndex ("api-index-full", \%IndexEntriesFull);
1194 #############################################################################
1195 # Function    : OutputDeprecatedIndex
1196 # Description : This writes the deprecated api indexlist that can be included
1197 #               into the main document into an <index> tag.
1198 #############################################################################
1200 sub OutputDeprecatedIndex {
1201     &OutputIndex ("api-index-deprecated", \%IndexEntriesDeprecated);
1205 #############################################################################
1206 # Function    : OutputSinceIndexes
1207 # Description : This writes the 'since' api indexlists that can be included into
1208 #               the main document into an <index> tag.
1209 #############################################################################
1211 sub OutputSinceIndexes {
1212     my @sinces = keys %{{ map { $_ => 1 } values %Since }};
1214     foreach my $version (@sinces) {
1215         @TRACE@("Since : [$version]\n");
1216         # TODO make filtered hash
1217         #my %index = grep { $Since{$_} eq $version } %IndexEntriesSince;
1218         my %index = map { $_ => $IndexEntriesSince{$_} } grep { $Since{$_} eq $version } keys %IndexEntriesSince;
1220         &OutputIndex ("api-index-$version", \%index);
1221     }
1224 #############################################################################
1225 # Function    : OutputAnnotationGlossary
1226 # Description : This writes a glossary of the used annotation terms into a
1227 #               separate glossary file that can be included into the main
1228 #               document.
1229 #############################################################################
1231 sub OutputAnnotationGlossary {
1232     my $old_glossary = "$DB_OUTPUT_DIR/annotation-glossary.xml";
1233     my $new_glossary = "$DB_OUTPUT_DIR/annotation-glossary.new";
1234     my $lastletter = " ";
1235     my $divopen = 0;
1237     # if there are no annotations used return
1238     return if (! keys(%AnnotationsUsed));
1240     # add acronyms that are referenced from acronym text
1241 rerun:
1242     foreach my $annotation (keys(%AnnotationsUsed)) {
1243         if(defined($AnnotationDefinition{$annotation})) {
1244             if($AnnotationDefinition{$annotation} =~ m/<acronym>([\w ]+)<\/acronym>/) {
1245                 if (!exists($AnnotationsUsed{$1})) {
1246                     $AnnotationsUsed{$1} = 1;
1247                     goto rerun;
1248                 }
1249             }
1250         }
1251     }
1253     open (OUTPUT, ">$new_glossary")
1254         || die "Can't create $new_glossary";
1256     print (OUTPUT  <<EOF);
1257 ${\( MakeDocHeader ("glossary") )}
1258 <glossary id="annotation-glossary">
1259   <title>Annotation Glossary</title>
1262     foreach my $annotation (sort({lc $a cmp lc $b} keys(%AnnotationsUsed))) {
1263         if(defined($AnnotationDefinition{$annotation})) {
1264             my $def = $AnnotationDefinition{$annotation};
1265             my $curletter = uc(substr($annotation,0,1));
1267             if ($curletter ne $lastletter) {
1268                 $lastletter = $curletter;
1270                 if ($divopen == 1) {
1271                     print (OUTPUT "</glossdiv>\n");
1272                 }
1273                 print (OUTPUT "<glossdiv><title>$curletter</title>\n");
1274                 $divopen = 1;
1275             }
1276             print (OUTPUT <<EOF);
1277     <glossentry>
1278       <glossterm><anchor id="annotation-glossterm-$annotation"/>$annotation</glossterm>
1279       <glossdef>
1280         <para>$def</para>
1281       </glossdef>
1282     </glossentry>
1284         }
1285     }
1287     if ($divopen == 1) {
1288         print (OUTPUT "</glossdiv>\n");
1289     }
1290     print (OUTPUT "</glossary>\n");
1291     close (OUTPUT);
1293     &UpdateFileIfChanged ($old_glossary, $new_glossary, 0);
1296 #############################################################################
1297 # Function    : ReadKnownSymbols
1298 # Description : This collects the names of non-private symbols from the
1299 #               $MODULE-sections.txt file.
1300 # Arguments   : $file - the $MODULE-sections.txt file which contains all of
1301 #                the functions/macros/structs etc. being documented, organised
1302 #                into sections and subsections.
1303 #############################################################################
1305 sub ReadKnownSymbols {
1306     my ($file) = @_;
1308     my $subsection = "";
1310     @TRACE@("Reading: $file\n");
1311     open (INPUT, $file)
1312         || die "Can't open $file: $!";
1314     while (<INPUT>) {
1315         if (m/^#/) {
1316             next;
1318         } elsif (m/^<SECTION>/) {
1319             $subsection = "";
1321         } elsif (m/^<SUBSECTION\s*(.*)>/i) {
1322             $subsection = $1;
1324         } elsif (m/^<SUBSECTION>/) {
1325             next;
1327         } elsif (m/^<TITLE>(.*)<\/TITLE>/) {
1328             next;
1330         } elsif (m/^<FILE>(.*)<\/FILE>/) {
1331             $KnownSymbols{"$TMPL_DIR/$1:Long_Description"} = 1;
1332             $KnownSymbols{"$TMPL_DIR/$1:Short_Description"} = 1;
1333             next;
1335         } elsif (m/^<INCLUDE>(.*)<\/INCLUDE>/) {
1336             next;
1338         } elsif (m/^<\/SECTION>/) {
1339             next;
1341         } elsif (m/^(\S+)/) {
1342             my $symbol = $1;
1344             if ($subsection ne "Standard" && $subsection ne "Private") {
1345                 $KnownSymbols{$symbol} = 1;
1346             }
1347             else {
1348                 $KnownSymbols{$symbol} = 0;
1349             }
1350         }
1351     }
1352     close (INPUT);
1356 #############################################################################
1357 # Function    : OutputDeclaration
1358 # Description : Returns the synopsis and detailed description DocBook
1359 #                describing one function/macro etc.
1360 # Arguments   : $symbol - the name of the function/macro begin described.
1361 #                $declaration - the declaration of the function/macro.
1362 #############################################################################
1364 sub OutputDeclaration {
1365     my ($symbol, $declaration) = @_;
1367     my $type = $DeclarationTypes {$symbol};
1368     if ($type eq 'MACRO') {
1369         return &OutputMacro ($symbol, $declaration);
1370     } elsif ($type eq 'TYPEDEF') {
1371         return &OutputTypedef ($symbol, $declaration);
1372     } elsif ($type eq 'STRUCT') {
1373         return &OutputStruct ($symbol, $declaration);
1374     } elsif ($type eq 'ENUM') {
1375         return &OutputEnum ($symbol, $declaration);
1376     } elsif ($type eq 'UNION') {
1377         return &OutputUnion ($symbol, $declaration);
1378     } elsif ($type eq 'VARIABLE') {
1379         return &OutputVariable ($symbol, $declaration);
1380     } elsif ($type eq 'FUNCTION') {
1381         return &OutputFunction ($symbol, $declaration, $type);
1382     } elsif ($type eq 'USER_FUNCTION') {
1383         return &OutputFunction ($symbol, $declaration, $type);
1384     } else {
1385         die "Unknown symbol type";
1386     }
1390 #############################################################################
1391 # Function    : OutputSymbolTraits
1392 # Description : Returns the Since and StabilityLevel paragraphs for a symbol.
1393 # Arguments   : $symbol - the name of the function/macro begin described.
1394 #############################################################################
1396 sub OutputSymbolTraits {
1397     my ($symbol) = @_;
1398     my $desc = "";
1400     if (exists $Since{$symbol}) {
1401         my $link_id = "api-index-".$Since{$symbol};
1402         $desc .= "<para role=\"since\">Since: <link linkend=\"$link_id\">$Since{$symbol}</link></para>";
1403     }
1404     if (exists $StabilityLevel{$symbol}) {
1405         my $stability = $StabilityLevel{$symbol};
1406         $AnnotationsUsed{$stability} = 1;
1407         $desc .= "<para role=\"stability\">Stability Level: <acronym>$stability</acronym></para>";
1408     }
1409     return $desc;
1412 #############################################################################
1413 # Function    : Output{Symbol,Section}ExtraLinks
1414 # Description : Returns extralinks for the symbol (if enabled).
1415 # Arguments   : $symbol - the name of the function/macro begin described.
1416 #############################################################################
1418 sub uri_escape {
1419     my $text = $_[0];
1420     return undef unless defined $text;
1422     # Build a char to hex map
1423     my %escapes = ();
1424     for (0..255) {
1425             $escapes{chr($_)} = sprintf("%%%02X", $_);
1426     }
1428     # Default unsafe characters.  RFC 2732 ^(uric - reserved)
1429     $text =~ s/([^A-Za-z0-9\-_.!~*'()])/$escapes{$1}/g;
1431     return $text;
1434 sub OutputSymbolExtraLinks {
1435     my ($symbol) = @_;
1436     my $desc = "";
1438     if (0) { # NEW FEATURE: needs configurability
1439     my $sstr = &uri_escape($symbol);
1440     my $mstr = &uri_escape($MODULE);
1441     $desc .= <<EOF;
1442 <ulink role="extralinks" url="http://www.google.com/codesearch?q=$sstr">code search</ulink>
1443 <ulink role="extralinks" url="http://library.gnome.org/edit?module=$mstr&amp;symbol=$sstr">edit documentation</ulink>
1445     }
1446     return $desc;
1449 sub OutputSectionExtraLinks {
1450     my ($symbol,$docsymbol) = @_;
1451     my $desc = "";
1453     if (0) { # NEW FEATURE: needs configurability
1454     my $sstr = &uri_escape($symbol);
1455     my $mstr = &uri_escape($MODULE);
1456     my $dsstr = &uri_escape($docsymbol);
1457     $desc .= <<EOF;
1458 <ulink role="extralinks" url="http://www.google.com/codesearch?q=$sstr">code search</ulink>
1459 <ulink role="extralinks" url="http://library.gnome.org/edit?module=$mstr&amp;symbol=$dsstr">edit documentation</ulink>
1461     }
1462     return $desc;
1466 #############################################################################
1467 # Function    : OutputMacro
1468 # Description : Returns the synopsis and detailed description of a macro.
1469 # Arguments   : $symbol - the macro.
1470 #                $declaration - the declaration of the macro.
1471 #############################################################################
1473 sub OutputMacro {
1474     my ($symbol, $declaration) = @_;
1475     my $id = &CreateValidSGMLID ($symbol);
1476     my $condition = &MakeConditionDescription ($symbol);
1477     my $synop = "<row><entry role=\"define_keyword\">#define</entry><entry role=\"function_name\"><link linkend=\"$id\">$symbol</link>";
1478     my $desc;
1480     my @fields = ParseMacroDeclaration($declaration, \&CreateValidSGML);
1481     my $title = $symbol . (@fields ? "()" : "");
1483     $desc = "<refsect2 id=\"$id\" role=\"macro\"$condition>\n<title>$title</title>\n";
1484     $desc .= MakeIndexterms($symbol, $id);
1485     $desc .= "\n";
1486     $desc .= OutputSymbolExtraLinks($symbol);
1488     if (@fields) {
1489         $synop .= "<phrase role=\"c_punctuation\">()</phrase>";
1490     }
1491     $synop .= "</entry></row>\n";
1493     # Don't output the macro definition if is is a conditional macro or it
1494     # looks like a function, i.e. starts with "g_" or "_?gnome_", or it is
1495     # longer than 2 lines, otherwise we get lots of complicated macros like
1496     # g_assert.
1497     if (!defined ($DeclarationConditional{$symbol}) && ($symbol !~ m/^g_/)
1498         && ($symbol !~ m/^_?gnome_/) && (($declaration =~ tr/\n//) < 2)) {
1499         my $decl_out = &CreateValidSGML ($declaration);
1500         $desc .= "<programlisting language=\"C\">$decl_out</programlisting>\n";
1501     } else {
1502         $desc .= "<programlisting language=\"C\">" . &MakeReturnField("#define") . "$symbol";
1503         if ($declaration =~ m/^\s*#\s*define\s+\w+(\([^\)]*\))/) {
1504             my $args = $1;
1505             my $pad = ' ' x ($RETURN_TYPE_FIELD_WIDTH - length ("#define "));
1506             # Align each line so that if should all line up OK.
1507             $args =~ s/\n/\n$pad/gm;
1508             $desc .= &CreateValidSGML ($args);
1509         }
1510         $desc .= "</programlisting>\n";
1511     }
1513     $desc .= &MakeDeprecationNote($symbol);
1515     my $parameters = &OutputParamDescriptions ("MACRO", $symbol, @fields);
1517     if (defined ($SymbolDocs{$symbol})) {
1518         my $symbol_docs = &ConvertMarkDown($symbol, $SymbolDocs{$symbol});
1519         $desc .= $symbol_docs;
1520     }
1522     $desc .= $parameters;
1523     $desc .= OutputSymbolTraits ($symbol);
1524     $desc .= "</refsect2>\n";
1525     return ($synop, $desc);
1529 #############################################################################
1530 # Function    : OutputTypedef
1531 # Description : Returns the synopsis and detailed description of a typedef.
1532 # Arguments   : $symbol - the typedef.
1533 #                $declaration - the declaration of the typedef,
1534 #                  e.g. 'typedef unsigned int guint;'
1535 #############################################################################
1537 sub OutputTypedef {
1538     my ($symbol, $declaration) = @_;
1539     my $id = &CreateValidSGMLID ($symbol);
1540     my $condition = &MakeConditionDescription ($symbol);
1541     my $desc = "<refsect2 id=\"$id\" role=\"typedef\"$condition>\n<title>$symbol</title>\n";
1542     my $synop = "<row><entry role=\"typedef_keyword\">typedef</entry><entry role=\"function_name\"><link linkend=\"$id\">$symbol</link></entry></row>\n";
1544     $desc .= MakeIndexterms($symbol, $id);
1545     $desc .= "\n";
1546     $desc .= OutputSymbolExtraLinks($symbol);
1548     if (!defined ($DeclarationConditional{$symbol})) {
1549         my $decl_out = &CreateValidSGML ($declaration);
1550         $desc .= "<programlisting language=\"C\">$decl_out</programlisting>\n";
1551     }
1553     $desc .= &MakeDeprecationNote($symbol);
1555     if (defined ($SymbolDocs{$symbol})) {
1556         $desc .= &ConvertMarkDown($symbol, $SymbolDocs{$symbol});
1557     }
1558     $desc .= OutputSymbolTraits ($symbol);
1559     $desc .= "</refsect2>\n";
1560     return ($synop, $desc);
1564 #############################################################################
1565 # Function    : OutputStruct
1566 # Description : Returns the synopsis and detailed description of a struct.
1567 #                We check if it is a object struct, and if so we only output
1568 #                parts of it that are noted as public fields.
1569 #                We also use a different IDs for object structs, since the
1570 #                original ID is used for the entire RefEntry.
1571 # Arguments   : $symbol - the struct.
1572 #                $declaration - the declaration of the struct.
1573 #############################################################################
1575 sub OutputStruct {
1576     my ($symbol, $declaration) = @_;
1578     my $is_gtype = 0;
1579     my $default_to_public = 1;
1580     if (&CheckIsObject ($symbol)) {
1581         @TRACE@("Found struct gtype: $symbol\n");
1582         $is_gtype = 1;
1583         $default_to_public = $ObjectRoots{$symbol} eq 'GBoxed';
1584     }
1586     my $id;
1587     my $condition;
1588     if ($is_gtype) {
1589         $id = &CreateValidSGMLID ($symbol . "_struct");
1590         $condition = &MakeConditionDescription ($symbol . "_struct");
1591     } else {
1592         $id = &CreateValidSGMLID ($symbol);
1593         $condition = &MakeConditionDescription ($symbol);
1594     }
1596     # Determine if it is a simple struct or it also has a typedef.
1597     my $has_typedef = 0;
1598     if ($StructHasTypedef{$symbol} || $declaration =~ m/^\s*typedef\s+/) {
1599       $has_typedef = 1;
1600     }
1602     my $type_output;
1603     my $desc;
1604     if ($has_typedef) {
1605         # For structs with typedefs we just output the struct name.
1606         $type_output = "";
1607         $desc = "<refsect2 id=\"$id\" role=\"struct\"$condition>\n<title>$symbol</title>\n";
1608     } else {
1609         $type_output = "struct";
1610         $desc = "<refsect2 id=\"$id\" role=\"struct\"$condition>\n<title>struct $symbol</title>\n";
1611     }
1612     my $synop = "<row><entry role=\"datatype_keyword\">${type_output}</entry><entry role=\"function_name\"><link linkend=\"$id\">$symbol</link></entry></row>\n";
1614     $desc .= MakeIndexterms($symbol, $id);
1615     $desc .= "\n";
1616     $desc .= OutputSymbolExtraLinks($symbol);
1618     # Form a pretty-printed, private-data-removed form of the declaration
1620     my $decl_out = "";
1621     if ($declaration =~ m/^\s*$/) {
1622         @TRACE@("Found opaque struct: $symbol\n");
1623         $decl_out = "typedef struct _$symbol $symbol;";
1624     } elsif ($declaration =~ m/^\s*struct\s+\w+\s*;\s*$/) {
1625         @TRACE@("Found opaque struct: $symbol\n");
1626         $decl_out = "struct $symbol;";
1627     } else {
1628         my $public = $default_to_public;
1629         my $new_declaration = "";
1630         my $decl_line;
1631         my $decl = $declaration;
1633         if ($decl =~ m/^\s*(typedef\s+)?struct\s*\w*\s*(?:\/\*.*\*\/)?\s*{(.*)}\s*\w*\s*;\s*$/s) {
1634             my $struct_contents = $2;
1636             foreach $decl_line (split (/\n/, $struct_contents)) {
1637                 @TRACE@("Struct line: $decl_line\n");
1638                 if ($decl_line =~ m%/\*\s*<\s*public\s*>\s*\*/%) {
1639                     $public = 1;
1640                 } elsif ($decl_line =~ m%/\*\s*<\s*(private|protected)\s*>\s*\*/%) {
1641                     $public = 0;
1642                 } elsif ($public) {
1643                     $new_declaration .= $decl_line . "\n";
1644                 }
1645             }
1647             if ($new_declaration) {
1648                 # Strip any blank lines off the ends.
1649                 $new_declaration =~ s/^\s*\n//;
1650                 $new_declaration =~ s/\n\s*$/\n/;
1652                 if ($has_typedef) {
1653                     $decl_out = "typedef struct {\n" . $new_declaration
1654                       . "} $symbol;\n";
1655                 } else {
1656                     $decl_out = "struct $symbol {\n" . $new_declaration
1657                       . "};\n";
1658                 }
1659             }
1660         } else {
1661             &LogWarning (&GetSymbolSourceFile ($symbol), &GetSymbolSourceLine($symbol),
1662                 "Couldn't parse struct:\n$declaration");
1663         }
1665         # If we couldn't parse the struct or it was all private, output an
1666         # empty struct declaration.
1667         if ($decl_out eq "") {
1668             if ($has_typedef) {
1669                 $decl_out = "typedef struct _$symbol $symbol;";
1670             } else {
1671                 $decl_out = "struct $symbol;";
1672             }
1673         }
1674     }
1676     $decl_out = &CreateValidSGML ($decl_out);
1677     $desc .= "<programlisting language=\"C\">$decl_out</programlisting>\n";
1679     $desc .= &MakeDeprecationNote($symbol);
1681     if (defined ($SymbolDocs{$symbol})) {
1682         $desc .= &ConvertMarkDown($symbol, $SymbolDocs{$symbol});
1683     }
1685     # Create a table of fields and descriptions
1687     # FIXME: Inserting &#160's into the produced type declarations here would
1688     #        improve the output in most situations ... except for function
1689     #        members of structs!
1690     my @fields = ParseStructDeclaration($declaration, !$default_to_public,
1691                                         0, \&MakeXRef,
1692                                         sub {
1693                                             "<structfield id=\"".&CreateValidSGMLID("$id.$_[0]")."\">$_[0]</structfield>";
1694                                         });
1695     my $params = $SymbolParams{$symbol};
1697     # If no parameters are filled in, we don't generate the description
1698     # table, for backwards compatibility.
1700     my $found = 0;
1701     if (defined $params) {
1702         for (my $i = 1; $i <= $#$params; $i += $PARAM_FIELD_COUNT) {
1703             if ($params->[$i] =~ /\S/) {
1704                 $found = 1;
1705                 last;
1706             }
1707         }
1708     }
1710     if ($found) {
1711         my %field_descrs = @$params;
1712         my $missing_parameters = "";
1713         my $unused_parameters = "";
1714         my $id = &CreateValidSGMLID ("$symbol".".members");
1716         $desc .= <<EOF;
1717 <refsect3 id="$id" role="struct_members">\n<title>Members</title>
1718 <informaltable role="struct_members_table" pgwide="1" frame="none">
1719 <tgroup cols="3">
1720 <colspec colname="struct_members_name" colwidth="300px"/>
1721 <colspec colname="struct_members_description"/>
1722 <colspec colname="struct_members_annotations" colwidth="200px"/>
1723 <tbody>
1726         while (@fields) {
1727             my $field_name = shift @fields;
1728             my $text = shift @fields;
1729             my $field_descr = $field_descrs{$field_name};
1730             my $param_annotations = "";
1732             $desc .= "<row role=\"member\"><entry role=\"struct_member_name\"><para>$text</para></entry>\n";
1733             if (defined $field_descr) {
1734                 ($field_descr,$param_annotations) = &ExpandAnnotation($symbol, $field_descr);
1735                 $field_descr = &ConvertMarkDown($symbol, $field_descr);
1736                 # trim
1737                 $field_descr =~ s/^(\s|\n)+//msg;
1738                 $field_descr =~ s/(\s|\n)+$//msg;
1739                 $desc .= "<entry role=\"struct_member_description\">$field_descr</entry>\n<entry role=\"struct_member_annotations\">$param_annotations</entry>\n";
1740                 delete $field_descrs{$field_name};
1741             } else {
1742                 &LogWarning (&GetSymbolSourceFile ($symbol), &GetSymbolSourceLine($symbol),
1743                     "Field description for $symbol"."::"."$field_name is missing in source code comment block.");
1744                 if ($missing_parameters ne "") {
1745                   $missing_parameters .= ", ".$field_name;
1746                 } else {
1747                     $missing_parameters = $field_name;
1748                 }
1749                 $desc .= "<entry /><entry />\n";
1750             }
1751             $desc .= "</row>\n";
1752         }
1753         $desc .= "</tbody></tgroup></informaltable>\n</refsect3>\n";
1754         foreach my $field_name (keys %field_descrs) {
1755             # Documenting those standard fields is not required anymore, but
1756             # we don't want to warn if they are documented anyway.
1757             if ($field_name =~ /(g_iface|parent_instance|parent_class)/) {
1758                 next;
1759             }
1760             &LogWarning (&GetSymbolSourceFile ($symbol), &GetSymbolSourceLine($symbol),
1761                 "Field description for $symbol"."::"."$field_name is not used from source code comment block.");
1762             if ($unused_parameters ne "") {
1763               $unused_parameters .= ", ".$field_name;
1764             } else {
1765                $unused_parameters = $field_name;
1766             }
1767         }
1769         # remember missing/unused parameters (needed in tmpl-free build)
1770         if (($missing_parameters ne "") and (! exists ($AllIncompleteSymbols{$symbol}))) {
1771             $AllIncompleteSymbols{$symbol}=$missing_parameters;
1772         }
1773         if (($unused_parameters ne "") and (! exists ($AllUnusedSymbols{$symbol}))) {
1774             $AllUnusedSymbols{$symbol}=$unused_parameters;
1775         }
1776     }
1777     else {
1778         if (scalar(@fields) > 0) {
1779             if (! exists ($AllIncompleteSymbols{$symbol})) {
1780                 $AllIncompleteSymbols{$symbol}="<items>";
1781                 &LogWarning (&GetSymbolSourceFile ($symbol), &GetSymbolSourceLine($symbol),
1782                     "Field descriptions for struct $symbol are missing in source code comment block.");
1783                 @TRACE@("Remaining structs fields: ".@fields.":".join(',',@fields)."\n");
1784             }
1785         }
1786     }
1788     $desc .= OutputSymbolTraits ($symbol);
1789     $desc .= "</refsect2>\n";
1790     return ($synop, $desc);
1794 #############################################################################
1795 # Function    : OutputUnion
1796 # Description : Returns the synopsis and detailed description of a union.
1797 # Arguments   : $symbol - the union.
1798 #                $declaration - the declaration of the union.
1799 #############################################################################
1801 sub OutputUnion {
1802     my ($symbol, $declaration) = @_;
1804     my $is_gtype = 0;
1805     if (&CheckIsObject ($symbol)) {
1806         @TRACE@("Found union gtype: $symbol\n");
1807         $is_gtype = 1;
1808     }
1810     my $id;
1811     my $condition;
1812     if ($is_gtype) {
1813         $id = &CreateValidSGMLID ($symbol . "_union");
1814         $condition = &MakeConditionDescription ($symbol . "_union");
1815     } else {
1816         $id = &CreateValidSGMLID ($symbol);
1817         $condition = &MakeConditionDescription ($symbol);
1818     }
1820     # Determine if it is a simple struct or it also has a typedef.
1821     my $has_typedef = 0;
1822     if ($StructHasTypedef{$symbol} || $declaration =~ m/^\s*typedef\s+/) {
1823       $has_typedef = 1;
1824     }
1826     my $type_output;
1827     my $desc;
1828     if ($has_typedef) {
1829         # For unions with typedefs we just output the union name.
1830         $type_output = "";
1831         $desc = "<refsect2 id=\"$id\" role=\"union\"$condition>\n<title>$symbol</title>\n";
1832     } else {
1833         $type_output = "union";
1834         $desc = "<refsect2 id=\"$id\" role=\"union\"$condition>\n<title>union $symbol</title>\n";
1835     }
1836     my $synop = "<row><entry role=\"datatype_keyword\">${type_output}</entry><entry role=\"function_name\"><link linkend=\"$id\">$symbol</link></entry></row>\n";
1838     $desc .= MakeIndexterms($symbol, $id);
1839     $desc .= "\n";
1840     $desc .= OutputSymbolExtraLinks($symbol);
1841     $desc .= &MakeDeprecationNote($symbol);
1843     if (defined ($SymbolDocs{$symbol})) {
1844         $desc .= &ConvertMarkDown($symbol, $SymbolDocs{$symbol});
1845     }
1847     # Create a table of fields and descriptions
1849     # FIXME: Inserting &#160's into the produced type declarations here would
1850     #        improve the output in most situations ... except for function
1851     #        members of structs!
1852     my @fields = ParseStructDeclaration($declaration, 0,
1853                                         0, \&MakeXRef,
1854                                         sub {
1855                                             "<structfield id=\"".&CreateValidSGMLID("$id.$_[0]")."\">$_[0]</structfield>";
1856                                         });
1857     my $params = $SymbolParams{$symbol};
1859     # If no parameters are filled in, we don't generate the description
1860     # table, for backwards compatibility
1862     my $found = 0;
1863     if (defined $params) {
1864         for (my $i = 1; $i <= $#$params; $i += $PARAM_FIELD_COUNT) {
1865             if ($params->[$i] =~ /\S/) {
1866                 $found = 1;
1867                 last;
1868             }
1869         }
1870     }
1872     if ($found) {
1873         my %field_descrs = @$params;
1874         my $missing_parameters = "";
1875         my $unused_parameters = "";
1876         my $id = &CreateValidSGMLID ("$symbol".".members");
1878         $desc .= <<EOF;
1879 <refsect3 id="$id" role="union_members">\n<title>Members</title>
1880 <informaltable role="union_members_table" pgwide="1" frame="none">
1881 <tgroup cols="3">
1882 <colspec colname="union_members_name" colwidth="300px"/>
1883 <colspec colname="union_members_description"/>
1884 <colspec colname="union_members_annotations" colwidth="200px"/>
1885 <tbody>
1888         while (@fields) {
1889             my $field_name = shift @fields;
1890             my $text = shift @fields;
1891             my $field_descr = $field_descrs{$field_name};
1892             my $param_annotations = "";
1894             $desc .= "<row><entry role=\"union_member_name\"><para>$text</para></entry>\n";
1895             if (defined $field_descr) {
1896                 ($field_descr,$param_annotations) = &ExpandAnnotation($symbol, $field_descr);
1897                 $field_descr = &ConvertMarkDown($symbol, $field_descr);
1899                 # trim
1900                 $field_descr =~ s/^(\s|\n)+//msg;
1901                 $field_descr =~ s/(\s|\n)+$//msg;
1902                 $desc .= "<entry role=\"union_member_description\">$field_descr</entry>\n<entry role=\"union_member_annotations\">$param_annotations</entry>\n";
1903                 delete $field_descrs{$field_name};
1904             } else {
1905                 &LogWarning (&GetSymbolSourceFile ($symbol), &GetSymbolSourceLine($symbol),
1906                     "Field description for $symbol"."::"."$field_name is missing in source code comment block.");
1907                 if ($missing_parameters ne "") {
1908                     $missing_parameters .= ", ".$field_name;
1909                 } else {
1910                     $missing_parameters = $field_name;
1911                 }
1912                 $desc .= "<entry /><entry />\n";
1913             }
1914             $desc .= "</row>\n";
1915         }
1916         $desc .= "</tbody></tgroup></informaltable>\n</refsect3>";
1917         foreach my $field_name (keys %field_descrs) {
1918             &LogWarning (&GetSymbolSourceFile ($symbol), &GetSymbolSourceLine($symbol),
1919                 "Field description for $symbol"."::"."$field_name is not used from source code comment block.");
1920             if ($unused_parameters ne "") {
1921               $unused_parameters .= ", ".$field_name;
1922             } else {
1923                $unused_parameters = $field_name;
1924             }
1925         }
1927         # remember missing/unused parameters (needed in tmpl-free build)
1928         if (($missing_parameters ne "") and (! exists ($AllIncompleteSymbols{$symbol}))) {
1929             $AllIncompleteSymbols{$symbol}=$missing_parameters;
1930         }
1931         if (($unused_parameters ne "") and (! exists ($AllUnusedSymbols{$symbol}))) {
1932             $AllUnusedSymbols{$symbol}=$unused_parameters;
1933         }
1934     }
1935     else {
1936         if (scalar(@fields) > 0) {
1937             if (! exists ($AllIncompleteSymbols{$symbol})) {
1938                 $AllIncompleteSymbols{$symbol}="<items>";
1939                 &LogWarning (&GetSymbolSourceFile ($symbol), &GetSymbolSourceLine($symbol),
1940                     "Field descriptions for union $symbol are missing in source code comment block.");
1941                 @TRACE@("Remaining union fields: ".@fields.":".join(',',@fields)."\n");
1942             }
1943         }
1944     }
1946     $desc .= OutputSymbolTraits ($symbol);
1947     $desc .= "</refsect2>\n";
1948     return ($synop, $desc);
1952 #############################################################################
1953 # Function    : OutputEnum
1954 # Description : Returns the synopsis and detailed description of a enum.
1955 # Arguments   : $symbol - the enum.
1956 #                $declaration - the declaration of the enum.
1957 #############################################################################
1959 sub OutputEnum {
1960     my ($symbol, $declaration) = @_;
1962     my $is_gtype = 0;
1963     if (&CheckIsObject ($symbol)) {
1964         @TRACE@("Found enum gtype: $symbol\n");
1965         $is_gtype = 1;
1966     }
1968     my $id;
1969     my $condition;
1970     if ($is_gtype) {
1971         $id = &CreateValidSGMLID ($symbol . "_enum");
1972         $condition = &MakeConditionDescription ($symbol . "_enum");
1973     } else {
1974         $id = &CreateValidSGMLID ($symbol);
1975         $condition = &MakeConditionDescription ($symbol);
1976     }
1978     my $synop = "<row><entry role=\"datatype_keyword\">enum</entry><entry role=\"function_name\"><link linkend=\"$id\">$symbol</link></entry></row>\n";
1979     my $desc = "<refsect2 id=\"$id\" role=\"enum\"$condition>\n<title>enum $symbol</title>\n";
1981     $desc .= MakeIndexterms($symbol, $id);
1982     $desc .= "\n";
1983     $desc .= OutputSymbolExtraLinks($symbol);
1984     $desc .= &MakeDeprecationNote($symbol);
1986     if (defined ($SymbolDocs{$symbol})) {
1987         $desc .= &ConvertMarkDown($symbol, $SymbolDocs{$symbol});
1988     }
1990     # Create a table of fields and descriptions
1992     my @fields = ParseEnumDeclaration($declaration);
1993     my $params = $SymbolParams{$symbol};
1995     # If nothing at all is documented log a single summary warning at the end.
1996     # Otherwise, warn about each undocumented item.
1998     my $found = 0;
1999     if (defined $params) {
2000         for (my $i = 1; $i <= $#$params; $i += $PARAM_FIELD_COUNT) {
2001             if ($params->[$i] =~ /\S/) {
2002                 $found = 1;
2003                 last;
2004             }
2005         }
2006     }
2008     my %field_descrs = (defined $params ? @$params : ());
2009     my $missing_parameters = "";
2010     my $unused_parameters = "";
2012     $id = &CreateValidSGMLID ("$symbol".".members");
2013     $desc .= <<EOF;
2014 <refsect3 id="$id" role="enum_members">\n<title>Members</title>
2015 <informaltable role="enum_members_table" pgwide="1" frame="none">
2016 <tgroup cols="3">
2017 <colspec colname="enum_members_name" colwidth="300px"/>
2018 <colspec colname="enum_members_description"/>
2019 <colspec colname="enum_members_annotations" colwidth="200px"/>
2020 <tbody>
2023     for my $field_name (@fields) {
2024         my $field_descr = $field_descrs{$field_name};
2025         my $param_annotations = "";
2027         $id = &CreateValidSGMLID ($field_name);
2028         $condition = &MakeConditionDescription ($field_name);
2029         $desc .= "<row role=\"constant\"><entry role=\"enum_member_name\"><para id=\"$id\">$field_name</para></entry>\n";
2030         if (defined $field_descr) {
2031             ($field_descr,$param_annotations) = &ExpandAnnotation($symbol, $field_descr);
2032             $field_descr = &ConvertMarkDown($symbol, $field_descr);
2033             $desc .= "<entry role=\"enum_member_description\">$field_descr</entry>\n<entry role=\"enum_member_annotations\">$param_annotations</entry>\n";
2034             delete $field_descrs{$field_name};
2035         } else {
2036             if ($found) {
2037                 &LogWarning (&GetSymbolSourceFile ($symbol), &GetSymbolSourceLine($symbol),
2038                     "Value description for $symbol"."::"."$field_name is missing in source code comment block.");
2039                 if ($missing_parameters ne "") {
2040                     $missing_parameters .= ", ".$field_name;
2041                 } else {
2042                     $missing_parameters = $field_name;
2043                 }
2044             }
2045             $desc .= "<entry /><entry />\n";
2046         }
2047         $desc .= "</row>\n";
2048     }
2049     $desc .= "</tbody></tgroup></informaltable>\n</refsect3>";
2050     foreach my $field_name (keys %field_descrs) {
2051         &LogWarning (&GetSymbolSourceFile ($symbol), &GetSymbolSourceLine($symbol),
2052             "Value description for $symbol"."::"."$field_name is not used from source code comment block.");
2053         if ($unused_parameters ne "") {
2054             $unused_parameters .= ", ".$field_name;
2055         } else {
2056             $unused_parameters = $field_name;
2057         }
2058     }
2060     # remember missing/unused parameters (needed in tmpl-free build)
2061     if (($missing_parameters ne "") and (! exists ($AllIncompleteSymbols{$symbol}))) {
2062         $AllIncompleteSymbols{$symbol}=$missing_parameters;
2063     }
2064     if (($unused_parameters ne "") and (! exists ($AllUnusedSymbols{$symbol}))) {
2065         $AllUnusedSymbols{$symbol}=$unused_parameters;
2066     }
2068     if (!$found) {
2069         if (scalar(@fields) > 0) {
2070             if (! exists ($AllIncompleteSymbols{$symbol})) {
2071                 $AllIncompleteSymbols{$symbol}="<items>";
2072                 &LogWarning (&GetSymbolSourceFile ($symbol), &GetSymbolSourceLine($symbol),
2073                     "Value descriptions for $symbol are missing in source code comment block.");
2074             }
2075         }
2076     }
2078     $desc .= OutputSymbolTraits ($symbol);
2079     $desc .= "</refsect2>\n";
2080     return ($synop, $desc);
2084 #############################################################################
2085 # Function    : OutputVariable
2086 # Description : Returns the synopsis and detailed description of a variable.
2087 # Arguments   : $symbol - the extern'ed variable.
2088 #                $declaration - the declaration of the variable.
2089 #############################################################################
2091 sub OutputVariable {
2092     my ($symbol, $declaration) = @_;
2093     my $id = &CreateValidSGMLID ($symbol);
2094     my $condition = &MakeConditionDescription ($symbol);
2096     @TRACE@("ouputing variable: '$symbol' '$declaration'");
2098     my $type_output;
2099     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*;/) {
2100         my $mod1 = defined ($1) ? $1 : "";
2101         my $ptr = defined ($3) ? $3 : "";
2102         my $space = defined ($4) ? $4 : "";
2103         my $mod2 = defined ($5) ? $5 : "";
2104         $type_output = "extern $mod1$ptr$space$mod2";
2105     } 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*=/) {
2106         my $mod1 = defined ($1) ? $1 : "";
2107         my $ptr = defined ($3) ? $3 : "";
2108         my $space = defined ($4) ? $4 : "";
2109         my $mod2 = defined ($5) ? $5 : "";
2110         $type_output = "$mod1$ptr$space$mod2";
2111     } else {
2112         $type_output = "extern";
2113     }
2114     my $synop = "<row><entry role=\"variable_type\">${type_output}</entry><entry role=\"function_name\"><link linkend=\"$id\">$symbol</link></entry></row>\n";
2116     my $desc = "<refsect2 id=\"$id\" role=\"variable\"$condition>\n<title>$symbol</title>\n";
2118     $desc .= MakeIndexterms($symbol, $id);
2119     $desc .= "\n";
2120     $desc .= OutputSymbolExtraLinks($symbol);
2122     my $decl_out = &CreateValidSGML ($declaration);
2123     $desc .= "<programlisting language=\"C\">$decl_out</programlisting>\n";
2125     $desc .= &MakeDeprecationNote($symbol);
2127     if (defined ($SymbolDocs{$symbol})) {
2128         $desc .= &ConvertMarkDown($symbol, $SymbolDocs{$symbol});
2129     }
2130     if (defined ($SymbolAnnotations{$symbol})) {
2131         my $param_desc = $SymbolAnnotations{$symbol};
2132         my $param_annotations = "";
2133         ($param_desc,$param_annotations) = &ExpandAnnotation($symbol, $param_desc);
2134         if ($param_annotations ne "") {
2135             $desc .= "\n<para>$param_annotations</para>";
2136         }
2137     }
2139     $desc .= OutputSymbolTraits ($symbol);
2140     $desc .= "</refsect2>\n";
2141     return ($synop, $desc);
2145 #############################################################################
2146 # Function    : OutputFunction
2147 # Description : Returns the synopsis and detailed description of a function.
2148 # Arguments   : $symbol - the function.
2149 #                $declaration - the declaration of the function.
2150 #############################################################################
2152 sub OutputFunction {
2153     my ($symbol, $declaration, $symbol_type) = @_;
2154     my $id = &CreateValidSGMLID ($symbol);
2155     my $condition = &MakeConditionDescription ($symbol);
2157     # Take out the return type     $1                                                                                       $2   $3
2158     $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//;
2159     my $type_modifier = defined($1) ? $1 : "";
2160     my $type = $2;
2161     my $pointer = $3;
2162     # Trim trailing spaces as we are going to pad to $RETURN_TYPE_FIELD_WIDTH below anyway
2163     $pointer =~ s/\s+$//;
2164     my $xref = &MakeXRef ($type, &tagify($type, "returnvalue"));
2165     my $start = "";
2166     #if ($symbol_type eq 'USER_FUNCTION') {
2167     #    $start = "typedef ";
2168     #}
2170     # We output const rather than G_CONST_RETURN.
2171     $type_modifier =~ s/G_CONST_RETURN/const/g;
2172     $pointer =~ s/G_CONST_RETURN/const/g;
2173     $pointer =~ s/^\s+/&#160;/g;
2175     my $ret_type_output;
2176     $ret_type_output = "$start$type_modifier$xref$pointer\n";
2178     my $indent_len;
2179     $indent_len = length ($symbol) + 2;
2180     my $char1 = my $char2 = my $char3 = "";
2181     if ($symbol_type eq 'USER_FUNCTION') {
2182         $indent_len += 3;
2183         $char1 = "<phrase role=\"c_punctuation\">(</phrase>";
2184         $char2 = "*";
2185         $char3 = "<phrase role=\"c_punctuation\">)</phrase>";
2186     }
2188     my ($symbol_output, $symbol_desc_output);
2189     $symbol_output = "$char1<link linkend=\"$id\">$char2$symbol</link>$char3";
2190     if ($indent_len < $MAX_SYMBOL_FIELD_WIDTH) {
2191         $symbol_desc_output = "$char1$char2$symbol$char3 ";
2192     } else {
2193         $indent_len = $MAX_SYMBOL_FIELD_WIDTH - 8;
2194         $symbol_desc_output = "$char1$char2$symbol$char3\n"
2195           . (' ' x ($indent_len - 1));
2196     }
2198     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";
2200     my $desc = "<refsect2 id=\"$id\" role=\"function\"$condition>\n<title>${symbol}&#160;()</title>\n";
2202     $desc .= MakeIndexterms($symbol, $id);
2203     $desc .= "\n";
2204     $desc .= OutputSymbolExtraLinks($symbol);
2206     $desc  .= "<programlisting language=\"C\">${ret_type_output}$symbol_desc_output(";
2208     my @fields = ParseFunctionDeclaration($declaration, \&MakeXRef,
2209                                         sub {
2210                                             &tagify($_[0],"parameter");
2211                                         });
2213     for (my $i = 1; $i <= $#fields; $i += 2) {
2214         my $field_name = $fields[$i];
2216         if ($i == 1) {
2217             $desc  .= "$field_name";
2218         } else {
2219             $desc  .= ",\n"
2220                 . (' ' x $indent_len)
2221                 . "$field_name";
2222         }
2224     }
2226     $desc  .= ");</programlisting>\n";
2228     $desc .= &MakeDeprecationNote($symbol);
2230     if (defined ($SymbolDocs{$symbol})) {
2231         $desc .= &ConvertMarkDown($symbol, $SymbolDocs{$symbol});
2232     }
2233     if (defined ($SymbolAnnotations{$symbol})) {
2234         my $param_desc = $SymbolAnnotations{$symbol};
2235         my $param_annotations = "";
2236         ($param_desc,$param_annotations) = &ExpandAnnotation($symbol, $param_desc);
2237         if ($param_annotations ne "") {
2238             $desc .= "\n<para>$param_annotations</para>";
2239         }
2240     }
2242     $desc .= &OutputParamDescriptions ("FUNCTION", $symbol, @fields);
2243     $desc .= OutputSymbolTraits ($symbol);
2244     $desc .= "</refsect2>\n";
2245     return ($synop, $desc);
2249 #############################################################################
2250 # Function    : OutputParamDescriptions
2251 # Description : Returns the DocBook output describing the parameters of a
2252 #                function, macro or signal handler.
2253 # Arguments   : $symbol_type - 'FUNCTION', 'MACRO' or 'SIGNAL'. Signal
2254 #                  handlers have an implicit user_data parameter last.
2255 #                $symbol - the name of the function/macro being described.
2256 #               @fields - parsed fields from the declaration, used to determine
2257 #                  undocumented/unused entries
2258 #############################################################################
2260 sub OutputParamDescriptions {
2261     my ($symbol_type, $symbol, @fields) = @_;
2262     my $output = "";
2263     my $params = $SymbolParams{$symbol};
2264     my $num_params = 0;
2265     my %field_descrs = ();
2267     if (@fields) {
2268         %field_descrs = @fields;
2269         delete $field_descrs{"void"};
2270         delete $field_descrs{"Returns"};
2271     }
2273     if (defined $params) {
2274         my $returns = "";
2275         my $params_desc = "";
2276         my $missing_parameters = "";
2277         my $unused_parameters = "";
2278         my $j;
2280         for ($j = 0; $j <= $#$params; $j += $PARAM_FIELD_COUNT) {
2281             my $param_name = $$params[$j];
2282             my $param_desc = $$params[$j + 1];
2283             my $param_annotations = "";
2285             ($param_desc,$param_annotations) = &ExpandAnnotation($symbol, $param_desc);
2286             $param_desc = &ConvertMarkDown($symbol, $param_desc);
2287             # trim
2288             $param_desc =~ s/^(\s|\n)+//msg;
2289             $param_desc =~ s/(\s|\n)+$//msg;
2290             if ($param_name eq "Returns") {
2291                 $returns = $param_desc;
2292                 if ($param_annotations ne "") {
2293                     $returns .= "\n<para>$param_annotations</para>";
2294                 }
2295             } elsif ($param_name eq "void") {
2296                 # FIXME: &LogWarning()?
2297                 @TRACE@("!!!! void in params for $symbol?\n");
2298             } else {
2299                 if (@fields) {
2300                     if (!defined $field_descrs{$param_name}) {
2301                         &LogWarning (&GetSymbolSourceFile ($symbol), &GetSymbolSourceLine($symbol),
2302                             "Parameter description for $symbol"."::"."$param_name is not used from source code comment block.");
2303                         if ($unused_parameters ne "") {
2304                           $unused_parameters .= ", ".$param_name;
2305                         } else {
2306                            $unused_parameters = $param_name;
2307                         }
2308                     } else {
2309                         delete $field_descrs{$param_name};
2310                     }
2311                 }
2312                 if($param_desc ne "") {
2313                     $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";
2314                     $num_params++;
2315                 }
2316             }
2317         }
2318         foreach my $param_name (keys %field_descrs) {
2319             &LogWarning (&GetSymbolSourceFile ($symbol), &GetSymbolSourceLine($symbol),
2320                 "Parameter description for $symbol"."::"."$param_name is missing in source code comment block.");
2321             if ($missing_parameters ne "") {
2322               $missing_parameters .= ", ".$param_name;
2323             } else {
2324                $missing_parameters = $param_name;
2325             }
2326         }
2328         # Signals have an implicit user_data parameter which we describe.
2329         if ($symbol_type eq "SIGNAL") {
2330             $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";
2331         }
2333         # Start a table if we need one.
2334         if ($params_desc ne "") {
2335           my $id = &CreateValidSGMLID ("$symbol".".parameters");
2337           $output .= <<EOF;
2338 <refsect3 id="$id" role="parameters">\n<title>Parameters</title>
2339 <informaltable role="parameters_table" pgwide="1" frame="none">
2340 <tgroup cols="3">
2341 <colspec colname="parameters_name" colwidth="150px"/>
2342 <colspec colname="parameters_description"/>
2343 <colspec colname="parameters_annotations" colwidth="200px"/>
2344 <tbody>
2346           $output .= $params_desc;
2347           $output .= "</tbody></tgroup></informaltable>\n</refsect3>";
2348         }
2350         # Output the returns info last
2351         if ($returns ne "") {
2352           my $id = &CreateValidSGMLID ("$symbol".".returns");
2354           $output .= <<EOF;
2355 <refsect3 id="$id" role=\"returns\">\n<title>Returns</title>
2357           $output .= $returns;
2358           $output .= "\n</refsect3>";
2359         }
2361         # remember missing/unused parameters (needed in tmpl-free build)
2362         if (($missing_parameters ne "") and (! exists ($AllIncompleteSymbols{$symbol}))) {
2363             $AllIncompleteSymbols{$symbol}=$missing_parameters;
2364         }
2365         if (($unused_parameters ne "") and (! exists ($AllUnusedSymbols{$symbol}))) {
2366             $AllUnusedSymbols{$symbol}=$unused_parameters;
2367         }
2368     }
2369     if (($num_params == 0) && @fields && (scalar(keys(%field_descrs)) > 0)) {
2370         if (! exists ($AllIncompleteSymbols{$symbol})) {
2371             $AllIncompleteSymbols{$symbol}="<parameters>";
2372         }
2373     }
2375     return $output;
2379 #############################################################################
2380 # Function    : ParseStabilityLevel
2381 # Description : Parses a stability level and outputs a warning if it isn't
2382 #               valid.
2383 # Arguments   : $stability - the stability text.
2384 #                $file, $line - context for error message
2385 #                $message - description of where the level is from, to use in
2386 #               any error message.
2387 # Returns     : The parsed stability level string.
2388 #############################################################################
2390 sub ParseStabilityLevel {
2391     my ($stability, $file, $line, $message) = @_;
2393     $stability =~ s/^\s*//;
2394     $stability =~ s/\s*$//;
2395     if ($stability =~ m/^stable$/i) {
2396         $stability = "Stable";
2397     } elsif ($stability =~ m/^unstable$/i) {
2398         $stability = "Unstable";
2399     } elsif ($stability =~ m/^private$/i) {
2400         $stability = "Private";
2401     } else {
2402         &LogWarning ($file, $line, "$message is $stability.".
2403             "It should be one of these: Stable, Unstable, or Private.");
2404     }
2405     return $stability;
2409 #############################################################################
2410 # Function    : OutputDBFile
2411 # Description : Outputs the final DocBook file for one section.
2412 # Arguments   : $file - the name of the file.
2413 #               $title - the title from the $MODULE-sections.txt file, which
2414 #                 will be overridden by the title in the template file.
2415 #               $section_id - the id to use for the toplevel tag.
2416 #               $includes - comma-separates list of include files added at top of
2417 #                 synopsis, with '<' '>' around them (if not already enclosed in "").
2418 #               $functions_synop - reference to the DocBook for the Functions Synopsis part.
2419 #               $other_synop - reference to the DocBook for the Types and Values Synopsis part.
2420 #               $functions_details - reference to the DocBook for the Functions Details part.
2421 #               $other_details - reference to the DocBook for the Types and Values Details part.
2422 #               $signal_synop - reference to the DocBook for the Signal Synopsis part
2423 #               $signal_desc - reference to the DocBook for the Signal Description part
2424 #               $args_synop - reference to the DocBook for the Arg Synopsis part
2425 #               $args_desc - reference to the DocBook for the Arg Description part
2426 #               $hierarchy - reference to the DocBook for the Object Hierarchy part
2427 #               $interfaces - reference to the DocBook for the Interfaces part
2428 #               $implementations - reference to the DocBook for the Known Implementations part
2429 #               $prerequisites - reference to the DocBook for the Prerequisites part
2430 #               $derived - reference to the DocBook for the Derived Interfaces part
2431 #               $file_objects - reference to an array of objects in this file
2432 #############################################################################
2434 sub OutputDBFile {
2435     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) = @_;
2437     @TRACE@("Output docbook for file $file with title '$title'\n");
2439     # The edited title overrides the one from the sections file.
2440     my $new_title = $SymbolDocs{"$TMPL_DIR/$file:Title"};
2441     if (defined ($new_title) && $new_title !~ m/^\s*$/) {
2442         $title = $new_title;
2443         @TRACE@("Found title: $title\n");
2444     }
2445     my $short_desc = $SymbolDocs{"$TMPL_DIR/$file:Short_Description"};
2446     if (!defined ($short_desc) || $short_desc =~ m/^\s*$/) {
2447         $short_desc = "";
2448     } else {
2449         # Don't use ConvertMarkDown here for now since we don't want blocks
2450         $short_desc = &ExpandAbbreviations("$title:Short_description",
2451                                            $short_desc);
2452         @TRACE@("Found short_desc: $short_desc");
2453     }
2454     my $long_desc = $SymbolDocs{"$TMPL_DIR/$file:Long_Description"};
2455     if (!defined ($long_desc) || $long_desc =~ m/^\s*$/) {
2456         $long_desc = "";
2457     } else {
2458         $long_desc = &ConvertMarkDown("$title:Long_description",
2459                                           $long_desc);
2460         @TRACE@("Found long_desc: $long_desc");
2461     }
2462     my $see_also = $SymbolDocs{"$TMPL_DIR/$file:See_Also"};
2463     if (!defined ($see_also) || $see_also =~ m%^\s*(<para>)?\s*(</para>)?\s*$%) {
2464         $see_also = "";
2465     } else {
2466         $see_also = &ConvertMarkDown("$title:See_Also", $see_also);
2467         @TRACE@("Found see_also: $see_also");
2468     }
2469     if ($see_also) {
2470         $see_also = "<refsect1 id=\"$section_id.see-also\">\n<title>See Also</title>\n$see_also\n</refsect1>\n";
2471     }
2472     my $stability = $SymbolDocs{"$TMPL_DIR/$file:Stability_Level"};
2473     if (!defined ($stability) || $stability =~ m/^\s*$/) {
2474         $stability = "";
2475     } else {
2476         $stability = &ParseStabilityLevel($stability, $file, $., "Section stability level");
2477         @TRACE@("Found stability: $stability");
2478     }
2479     if ($stability) {
2480         $AnnotationsUsed{$stability} = 1;
2481         $stability = "<refsect1 id=\"$section_id.stability-level\">\n<title>Stability Level</title>\n<acronym>$stability</acronym>, unless otherwise indicated\n</refsect1>\n";
2482     } elsif ($DEFAULT_STABILITY) {
2483         $AnnotationsUsed{$DEFAULT_STABILITY} = 1;
2484         $stability = "<refsect1 id=\"$section_id.stability-level\">\n<title>Stability Level</title>\n<acronym>$DEFAULT_STABILITY</acronym>, unless otherwise indicated\n</refsect1>\n";
2485     }
2487     my $image = $SymbolDocs{"$TMPL_DIR/$file:Image"};
2488     if (!defined ($image) || $image =~ m/^\s*$/) {
2489       $image = "";
2490     } else {
2491       $image =~ s/^\s*//;
2492       $image =~ s/\s*$//;
2494       my $format;
2496       if ($image =~ /jpe?g$/i) {
2497         $format = "format='JPEG'";
2498       } elsif ($image =~ /png$/i) {
2499         $format = "format='PNG'";
2500       } elsif ($image =~ /svg$/i) {
2501         $format = "format='SVG'";
2502       } else {
2503         $format = "";
2504       }
2506       $image = "  <inlinegraphic fileref='$image' $format/>\n"
2507     }
2509     my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) =
2510         gmtime (time);
2511     my $month = (qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec))[$mon];
2512     $year += 1900;
2514     my $include_output = "";
2515     if ($includes) {
2516       $include_output .= "<refsect1 id=\"$section_id.includes\"><title>Includes</title><synopsis>";
2517       my $include;
2518       foreach $include (split (/,/, $includes)) {
2519         if ($include =~ m/^\".+\"$/) {
2520           $include_output .= "#include ${include}\n";
2521         }
2522         else {
2523           $include =~ s/^\s+|\s+$//gs;
2524           $include_output .= "#include &lt;${include}&gt;\n";
2525         }
2526       }
2527       $include_output .= "</synopsis></refsect1>\n";
2528     }
2530     my $extralinks = OutputSectionExtraLinks($title,"Section:$file");
2532     my $old_db_file = "$DB_OUTPUT_DIR/$file.xml";
2533     my $new_db_file = "$DB_OUTPUT_DIR/$file.xml.new";
2535     open (OUTPUT, ">$new_db_file")
2536         || die "Can't create $new_db_file: $!";
2538     my $object_anchors = "";
2539     foreach my $object (@$file_objects) {
2540         next if ($object eq $section_id);
2541         my $id = CreateValidSGMLID($object);
2542         @TRACE@("Adding anchor for $object\n");
2543         $object_anchors .= "<anchor id=\"$id\"/>";
2544     }
2546     # Make sure we produce valid docbook
2547     $$functions_details ||= "<para />";
2549     # We used to output this, but is messes up our UpdateFileIfChanged code
2550     # since it changes every day (and it is only used in the man pages):
2551     # "<refentry id="$section_id" revision="$mday $month $year">"
2553     print OUTPUT <<EOF;
2554 ${\( MakeDocHeader ("refentry") )}
2555 <refentry id="$section_id">
2556 <refmeta>
2557 <refentrytitle role="top_of_page" id="$section_id.top_of_page">$title</refentrytitle>
2558 <manvolnum>3</manvolnum>
2559 <refmiscinfo>\U$MODULE\E Library$image</refmiscinfo>
2560 </refmeta>
2561 <refnamediv>
2562 <refname>$title</refname>
2563 <refpurpose>$short_desc</refpurpose>
2564 </refnamediv>
2565 $stability
2566 $$functions_synop$$args_synop$$signals_synop$object_anchors$$other_synop$$hierarchy$$prerequisites$$derived$$interfaces$$implementations
2567 $include_output
2568 <refsect1 id="$section_id.description" role="desc">
2569 <title role="desc.title">Description</title>
2570 $extralinks$long_desc
2571 </refsect1>
2572 <refsect1 id="$section_id.functions_details" role="details">
2573 <title role="details.title">Functions</title>
2574 $$functions_details
2575 </refsect1>
2576 <refsect1 id="$section_id.other_details" role="details">
2577 <title role="details.title">Types and Values</title>
2578 $$other_details
2579 </refsect1>
2580 $$args_desc$$signals_desc$see_also
2581 </refentry>
2583     close (OUTPUT);
2585     return &UpdateFileIfChanged ($old_db_file, $new_db_file, 0);
2588 #############################################################################
2589 # Function    : OutputProgramDBFile
2590 # Description : Outputs the final DocBook file for one program.
2591 # Arguments   : $file - the name of the file.
2592 #               $section_id - the id to use for the toplevel tag.
2593 #############################################################################
2595 sub OutputProgramDBFile {
2596     my ($program, $section_id) = @_;
2598     @TRACE@("Output program docbook for $program\n");
2600     my $short_desc = $SourceSymbolDocs{"$TMPL_DIR/$program:Short_Description"};
2601     if (!defined ($short_desc) || $short_desc =~ m/^\s*$/) {
2602         $short_desc = "";
2603     } else {
2604         # Don't use ConvertMarkDown here for now since we don't want blocks
2605         $short_desc = &ExpandAbbreviations("$program", $short_desc);
2606         @TRACE@("Found short_desc: $short_desc");
2607     }
2609     my $synopsis = $SourceSymbolDocs{"$TMPL_DIR/$program:Synopsis"};
2610     if (defined ($synopsis) && $synopsis !~ m/^\s*$/) {
2611         my $i;
2612         my @items = split(' ', $synopsis);
2613         for ($i = 0; $i <= $#items; $i++) {
2614             my $parameter = $items[$i];
2615             my $choice = "plain";
2616             my $rep = "";
2618             # first parameter is the command name
2619             if ($i == 0) {
2620                 $synopsis = "<command>$parameter</command>\n";
2621                 next;
2622             }
2624             # square brackets indicate optional parameters, curly brackets
2625             # indicate required parameters ("plain" parameters are also
2626             # mandatory, but do not get extra decoration)
2627             if ($parameter =~ s/^\[(.+?)\]$/$1/) {
2628                 $choice = "opt";
2629             } elsif ($parameter =~ s/^\{(.+?)\}$/$1/) {
2630                 $choice = "req";
2631             }
2633             # parameters ending in "..." are repeatable
2634             if ($parameter =~ s/\.\.\.$//) {
2635                 $rep = " rep=\"repeat\"";
2636             }
2638             # italic parameters are replaceable parameters
2639             if ($parameter =~ s/\*(.+?)\*/$1/) {
2640                 $parameter = "<replaceable>$parameter</replaceable>";
2641             }
2643             $synopsis .= "<arg choice=\"$choice\"$rep>";
2644             $synopsis .= $parameter;
2645             $synopsis .= "</arg>\n";
2646         }
2648         @TRACE@("Found synopsis: $synopsis");
2649     } else {
2650         $synopsis = "<command>$program</command>";
2651     }
2653     my $long_desc = $SourceSymbolDocs{"$TMPL_DIR/$program:Long_Description"};
2654     if (!defined ($long_desc) || $long_desc =~ m/^\s*$/) {
2655         $long_desc = "";
2656     } else {
2657         $long_desc = &ConvertMarkDown("$program:Long_description", $long_desc);
2658         @TRACE@("Found long_desc: $long_desc");
2659     }
2661     my $options = "";
2662     if (defined ($SourceSymbolDocs{"$TMPL_DIR/$program:Options"})) {
2663         my @opts = @{ $SourceSymbolDocs{"$TMPL_DIR/$program:Options"} };
2664         my $k;
2666         $options = "<refsect1>\n<title>Options</title>\n<variablelist>\n";
2667         for ($k = 0; $k <= $#opts; $k += 2) {
2668             my $opt_desc = $opts[$k+1];
2669             my @opt_names;
2670             my $i;
2672             $opt_desc =~ s/\*(.+?)\*/<replaceable>$1<\/replaceable>/g;
2674             $options .= "<varlistentry>\n<term>";
2675             @opt_names = split (',', $opts[$k]);
2676             for ($i = 0; $i <= $#opt_names; $i++) {
2677                 my $prefix = ($i > 0) ? ", " : "";
2678                 $opt_names[$i] =~ s/\*(.+?)\*/<replaceable>$1<\/replaceable>/g;
2680                 $options .= "$prefix<option>$opt_names[$i]</option>\n";
2681             }
2682             $options .= "</term>\n";
2683             $options .= "<listitem><para>$opt_desc</para></listitem>\n";
2684             $options .= "</varlistentry>\n";
2685         }
2686         $options .= "</variablelist></refsect1>\n";
2687     }
2689     my $exit_status = $SourceSymbolDocs{"$TMPL_DIR/$program:Returns"};
2690     if (defined ($exit_status) && $exit_status ne "") {
2691         $exit_status = &ConvertMarkDown("$program:Returns", $exit_status);
2692         $exit_status = "<refsect1 id=\"$section_id.exit-status\">\n<title>Exit Status</title>\n$exit_status\n</refsect1>\n";
2693     } else {
2694         $exit_status = "";
2695     }
2697     my $see_also = $SourceSymbolDocs{"$TMPL_DIR/$program:See_Also"};
2698     if (!defined ($see_also) || $see_also =~ m%^\s*(<para>)?\s*(</para>)?\s*$%) {
2699         $see_also = "";
2700     } else {
2701         $see_also = &ConvertMarkDown("$program:See_Also", $see_also);
2702         @TRACE@("Found see_also: $see_also");
2703     }
2704     if ($see_also) {
2705         $see_also = "<refsect1 id=\"$section_id.see-also\">\n<title>See Also</title>\n$see_also\n</refsect1>\n";
2706     }
2708     my $old_db_file = "$DB_OUTPUT_DIR/$program.xml";
2709     my $new_db_file = "$DB_OUTPUT_DIR/$program.xml.new";
2711     open (OUTPUT, ">$new_db_file")
2712         || die "Can't create $new_db_file: $!";
2714     print OUTPUT <<EOF;
2715 ${\( MakeDocHeader ("refentry") )}
2716 <refentry id="$section_id">
2717 <refmeta>
2718 <refentrytitle role="top_of_page" id="$section_id.top_of_page">$program</refentrytitle>
2719 <manvolnum>1</manvolnum>
2720 <refmiscinfo>User Commands</refmiscinfo>
2721 </refmeta>
2722 <refnamediv>
2723 <refname>$program</refname>
2724 <refpurpose>$short_desc</refpurpose>
2725 </refnamediv>
2726 <refsynopsisdiv>
2727 <cmdsynopsis>$synopsis</cmdsynopsis>
2728 </refsynopsisdiv>
2729 <refsect1 id="$section_id.description" role="desc">
2730 <title role="desc.title">Description</title>
2731 $long_desc
2732 </refsect1>
2733 $options$exit_status$see_also
2734 </refentry>
2736     close (OUTPUT);
2738     return &UpdateFileIfChanged ($old_db_file, $new_db_file, 0);
2743 #############################################################################
2744 # Function    : OutputExtraFile
2745 # Description : Copies an "extra" DocBook file into the output directory,
2746 #               expanding abbreviations
2747 # Arguments   : $file - the source file.
2748 #############################################################################
2749 sub OutputExtraFile {
2750     my ($file) = @_;
2752     my $basename;
2754     ($basename = $file) =~ s!^.*/!!;
2756     my $old_db_file = "$DB_OUTPUT_DIR/$basename";
2757     my $new_db_file = "$DB_OUTPUT_DIR/$basename.new";
2759     my $contents;
2761     open(EXTRA_FILE, "<$file") || die "Can't open $file";
2763     {
2764         local $/;
2765         $contents = <EXTRA_FILE>;
2766     }
2768     open (OUTPUT, ">$new_db_file")
2769         || die "Can't create $new_db_file: $!";
2771     print OUTPUT &ExpandAbbreviations ("$basename file", $contents);
2772     close (OUTPUT);
2774     return &UpdateFileIfChanged ($old_db_file, $new_db_file, 0);
2776 #############################################################################
2777 # Function    : OutputBook
2778 # Description : Outputs the entities that need to be included into the
2779 #                main docbook file for the module.
2780 # Arguments   : $book_top - the declarations of the entities, which are added
2781 #                  at the top of the main docbook file.
2782 #                $book_bottom - the references to the entities, which are
2783 #                  added in the main docbook file at the desired position.
2784 #############################################################################
2786 sub OutputBook {
2787     my ($book_top, $book_bottom) = @_;
2789     my $old_file = "$DB_OUTPUT_DIR/$MODULE-doc.top";
2790     my $new_file = "$DB_OUTPUT_DIR/$MODULE-doc.top.new";
2792     open (OUTPUT, ">$new_file")
2793         || die "Can't create $new_file: $!";
2794     print OUTPUT $book_top;
2795     close (OUTPUT);
2797     &UpdateFileIfChanged ($old_file, $new_file, 0);
2800     $old_file = "$DB_OUTPUT_DIR/$MODULE-doc.bottom";
2801     $new_file = "$DB_OUTPUT_DIR/$MODULE-doc.bottom.new";
2803     open (OUTPUT, ">$new_file")
2804         || die "Can't create $new_file: $!";
2805     print OUTPUT $book_bottom;
2806     close (OUTPUT);
2808     &UpdateFileIfChanged ($old_file, $new_file, 0);
2811     # If the main docbook file hasn't been created yet, we create it here.
2812     # The user can tweak it later.
2813     if ($MAIN_SGML_FILE && ! -e $MAIN_SGML_FILE) {
2814         open (OUTPUT, ">$MAIN_SGML_FILE")
2815           || die "Can't create $MAIN_SGML_FILE: $!";
2817         print OUTPUT <<EOF;
2818 ${\( MakeDocHeader ("book") )}
2819 <book id="index">
2820   <bookinfo>
2821     <title>&package_name; Reference Manual</title>
2822     <releaseinfo>
2823       for &package_string;.
2824       The latest version of this documentation can be found on-line at
2825       <ulink role="online-location" url="http://[SERVER]/&package_name;/index.html">http://[SERVER]/&package_name;/</ulink>.
2826     </releaseinfo>
2827   </bookinfo>
2829   <chapter>
2830     <title>[Insert title here]</title>
2831     $book_bottom
2832   </chapter>
2834         if (-e $OBJECT_TREE_FILE) {
2835             print OUTPUT <<EOF;
2836   <chapter id="object-tree">
2837     <title>Object Hierarchy</title>
2838     <xi:include href="xml/tree_index.sgml"/>
2839   </chapter>
2841         } else {
2842             print OUTPUT <<EOF;
2843   <!-- enable this when you use gobject types
2844   <chapter id="object-tree">
2845     <title>Object Hierarchy</title>
2846     <xi:include href="xml/tree_index.sgml"/>
2847   </chapter>
2848   -->
2850         }
2851         print OUTPUT <<EOF;
2852   <index id="api-index-full">
2853     <title>API Index</title>
2854     <xi:include href="xml/api-index-full.xml"><xi:fallback /></xi:include>
2855   </index>
2856   <index id="deprecated-api-index" role="deprecated">
2857     <title>Index of deprecated API</title>
2858     <xi:include href="xml/api-index-deprecated.xml"><xi:fallback /></xi:include>
2859   </index>
2861         if (keys(%AnnotationsUsed)) {
2862             print OUTPUT <<EOF;
2863   <xi:include href="xml/annotation-glossary.xml"><xi:fallback /></xi:include>
2865         } else {
2866             print OUTPUT <<EOF;
2867   <!-- enable this when you use gobject introspection annotations
2868   <xi:include href="xml/annotation-glossary.xml"><xi:fallback /></xi:include>
2869   -->
2871         }
2872         print OUTPUT <<EOF;
2873 </book>
2876         close (OUTPUT);
2877     }
2881 #############################################################################
2882 # Function    : CreateValidSGML
2883 # Description : This turns any chars which are used in SGML into entities,
2884 #                e.g. '<' into '&lt;'
2885 # Arguments   : $text - the text to turn into proper SGML.
2886 #############################################################################
2888 sub CreateValidSGML {
2889     my ($text) = @_;
2890     $text =~ s/&/&amp;/g;        # Do this first, or the others get messed up.
2891     $text =~ s/</&lt;/g;
2892     $text =~ s/>/&gt;/g;
2893     # browers render single tabs inconsistently
2894     $text =~ s/([^\s])\t([^\s])/$1&#160;$2/g;
2895     return $text;
2898 #############################################################################
2899 # Function    : ConvertSGMLChars
2900 # Description : This is used for text in source code comment blocks, to turn
2901 #               chars which are used in SGML into entities, e.g. '<' into
2902 #               '&lt;'. Depending on $INLINE_MARKUP_MODE, this is done
2903 #               unconditionally or only if the character doesn't seem to be
2904 #               part of an SGML construct (tag or entity reference).
2905 # Arguments   : $text - the text to turn into proper SGML.
2906 #############################################################################
2908 sub ConvertSGMLChars {
2909     my ($symbol, $text) = @_;
2911     if ($INLINE_MARKUP_MODE) {
2912         # For the XML/SGML mode only convert to entities outside CDATA sections.
2913         return &ModifyXMLElements ($text, $symbol,
2914                                    "<!\\[CDATA\\[|<programlisting[^>]*>",
2915                                    \&ConvertSGMLCharsEndTag,
2916                                    \&ConvertSGMLCharsCallback);
2917     } else {
2918         # For the simple non-sgml mode, convert to entities everywhere.
2920         # First, convert freestanding & to &amp;
2921         $text =~ s/&(?![a-zA-Z#]+;)/&amp;/g;
2922         $text =~ s/</&lt;/g;
2923         # Allow ">" at beginning of string for blockquote markdown
2924         $text =~ s/(?<=[^\w\n"'\/-])>/&gt;/g;
2926         return $text;
2927     }
2931 sub ConvertSGMLCharsEndTag {
2932   if ($_[0] eq "<!\[CDATA\[") {
2933     return "]]>";
2934   } else {
2935     return "</programlisting>";
2936   }
2939 sub ConvertSGMLCharsCallback {
2940   my ($text, $symbol, $tag) = @_;
2942   if ($tag =~ m/^<programlisting/) {
2943     # We can handle <programlisting> specially here.
2944     return &ModifyXMLElements ($text, $symbol,
2945                                "<!\\[CDATA\\[",
2946                                \&ConvertSGMLCharsEndTag,
2947                                \&ConvertSGMLCharsCallback2);
2948   } elsif ($tag eq "") {
2949     # If we're not in CDATA convert to entities.
2950     $text =~ s/&(?![a-zA-Z#]+;)/&amp;/g;        # Do this first, or the others get messed up.
2951     $text =~ s/<(?![a-zA-Z\/!])/&lt;/g;
2952     # Allow ">" at beginning of string for blockquote markdown
2953     $text =~ s/(?<=[^\w\n"'\/-])>/&gt;/g;
2955     # Handle "#include <xxxxx>"
2956     $text =~ s/#include(\s+)<([^>]+)>/#include$1&lt;$2&gt;/g;
2957   }
2959   return $text;
2962 sub ConvertSGMLCharsCallback2 {
2963   my ($text, $symbol, $tag) = @_;
2965   # If we're not in CDATA convert to entities.
2966   # We could handle <programlisting> differently, though I'm not sure it helps.
2967   if ($tag eq "") {
2968     # replace only if its not a tag
2969     $text =~ s/&(?![a-zA-Z#]+;)/&amp;/g;        # Do this first, or the others get messed up.
2970     $text =~ s/<(?![a-zA-Z\/!])/&lt;/g;
2971     $text =~ s/(?<![a-zA-Z0-9"'\/-])>/&gt;/g;
2973     # Handle "#include <xxxxx>"
2974     $text =~ s/#include(\s+)<([^>]+)>/#include$1&lt;$2&gt;/g;
2975   }
2977   return $text;
2980 #############################################################################
2981 # Function    : ExpandAnnotation
2982 # Description : This turns annotations into acronym tags.
2983 # Arguments   : $symbol - the symbol being documented, for error messages.
2984 #                $text - the text to expand.
2985 #############################################################################
2986 sub ExpandAnnotation {
2987     my ($symbol, $param_desc) = @_;
2988     my $param_annotations = "";
2990     # look for annotations at the start of the comment part
2991     # function level annotations don't end with a colon ':'
2992     if ($param_desc =~ m%^\s*\((.*?)\)(:|$)%) {
2993         my @annotations;
2994         my $annotation;
2995         $param_desc = $';
2997         @annotations = split(/\)\s*\(/,$1);
2998         @TRACE@("annotations for $symbol: '$1'\n");
2999         foreach $annotation (@annotations) {
3000             # need to search for the longest key-match in %AnnotationDefinition
3001             my $match_length=0;
3002             my $match_annotation="";
3003             my $annotationdef;
3004             foreach $annotationdef (keys %AnnotationDefinition) {
3005                 if ($annotation =~ m/^$annotationdef/) {
3006                     if (length($annotationdef)>$match_length) {
3007                         $match_length=length($annotationdef);
3008                         $match_annotation=$annotationdef;
3009                     }
3010                 }
3011             }
3012             my $annotation_extra = "";
3013             if ($match_annotation ne "") {
3014                 if ($annotation =~ m%$match_annotation\s+(.*)%) {
3015                     $annotation_extra = " $1";
3016                 }
3017                 $AnnotationsUsed{$match_annotation} = 1;
3018                 $param_annotations .= "[<acronym>$match_annotation</acronym>$annotation_extra]";
3019             }
3020             else {
3021                 &LogWarning (&GetSymbolSourceFile ($symbol), &GetSymbolSourceLine($symbol),
3022                     "unknown annotation \"$annotation\" in documentation for $symbol.");
3023                 $param_annotations .= "[$annotation]";
3024             }
3025         }
3026         chomp($param_desc);
3027         $param_desc =~ m/^(.*?)\.*\s*$/s;
3028         $param_desc = "$1. ";
3029     }
3030     if ($param_annotations ne "") {
3031         $param_annotations = "<emphasis role=\"annotation\">$param_annotations</emphasis>";
3032     }
3033     return ($param_desc, $param_annotations);
3036 #############################################################################
3037 # Function    : ExpandAbbreviations
3038 # Description : This turns the abbreviations function(), macro(), @param,
3039 #                %constant, and #symbol into appropriate DocBook markup.
3040 #               CDATA sections and <programlisting> parts are skipped.
3041 # Arguments   : $symbol - the symbol being documented, for error messages.
3042 #                $text - the text to expand.
3043 #############################################################################
3045 sub ExpandAbbreviations {
3046   my ($symbol, $text) = @_;
3048   # Note: This is a fallback and normally done in the markdown parser
3050   # Convert "|[" and "]|" into the start and end of program listing examples.
3051   # Support \[<!-- language="C" --> modifiers
3052   $text =~ s%\|\[<!-- language="([^"]+)" -->%<informalexample><programlisting language="$1"><![CDATA[%g;
3053   $text =~ s%\|\[%<informalexample><programlisting><![CDATA[%g;
3054   $text =~ s%\]\|%]]></programlisting></informalexample>%g;
3056   # keep CDATA unmodified, preserve ulink tags (ideally we preseve all tags
3057   # as such)
3058   return &ModifyXMLElements ($text, $symbol,
3059                              "<!\\[CDATA\\[|<ulink[^>]*>|<programlisting[^>]*>|<!DOCTYPE",
3060                              \&ExpandAbbreviationsEndTag,
3061                              \&ExpandAbbreviationsCallback);
3065 # Returns the end tag (as a regexp) corresponding to the given start tag.
3066 sub ExpandAbbreviationsEndTag {
3067   my ($start_tag) = @_;
3069   if ($start_tag eq "<!\[CDATA\[") {
3070     return "]]>";
3071   } elsif ($start_tag eq "<!DOCTYPE") {
3072     return ">";
3073   } elsif ($start_tag =~ m/<(\w+)/) {
3074     return "</$1>";
3075   }
3078 # Called inside or outside each CDATA or <programlisting> section.
3079 sub ExpandAbbreviationsCallback {
3080   my ($text, $symbol, $tag) = @_;
3082   if ($tag =~ m/^<programlisting/) {
3083     # Handle any embedded CDATA sections.
3084     return &ModifyXMLElements ($text, $symbol,
3085                                "<!\\[CDATA\\[",
3086                                \&ExpandAbbreviationsEndTag,
3087                                \&ExpandAbbreviationsCallback2);
3088   } elsif ($tag eq "") {
3089     # NOTE: this is a fallback. It is normally done by the Markdown parser.
3091     # We are outside any CDATA or <programlisting> sections, so we expand
3092     # any gtk-doc abbreviations.
3094     # Convert '@param()'
3095     # FIXME: we could make those also links ($symbol.$2), but that would be less
3096     # useful as the link target is a few lines up or down
3097     $text =~ s/(\A|[^\\])\@(\w+((\.|->)\w+)*)\s*\(\)/$1<parameter>$2()<\/parameter>/g;
3099     # Convert 'function()' or 'macro()'.
3100     # if there is abc_*_def() we don't want to make a link to _def()
3101     # FIXME: also handle abc(def(....)) : but that would need to be done recursively :/
3102     $text =~ s/([^\*.\w])(\w+)\s*\(\)/$1.&MakeXRef($2, &tagify($2 . "()", "function"));/eg;
3103     # handle #Object.func()
3104     $text =~ s/(\A|[^\\])#([\w\-:\.]+[\w]+)\s*\(\)/$1.&MakeXRef($2, &tagify($2 . "()", "function"));/eg;
3106     # Convert '@param', but not '\@param'.
3107     $text =~ s/(\A|[^\\])\@(\w+((\.|->)\w+)*)/$1<parameter>$2<\/parameter>/g;
3108     $text =~ s/\\\@/\@/g;
3110     # Convert '%constant', but not '\%constant'.
3111     # Also allow negative numbers, e.g. %-1.
3112     $text =~ s/(\A|[^\\])\%(-?\w+)/$1.&MakeXRef($2, &tagify($2, "literal"));/eg;
3113     $text =~ s/\\\%/\%/g;
3115     # Convert '#symbol', but not '\#symbol'.
3116     $text =~ s/(\A|[^\\])#([\w\-:\.]+[\w]+)/$1.&MakeHashXRef($2, "type");/eg;
3117     $text =~ s/\\#/#/g;
3118   }
3120   return $text;
3123 # This is called inside a <programlisting>
3124 sub ExpandAbbreviationsCallback2 {
3125   my ($text, $symbol, $tag) = @_;
3127   if ($tag eq "") {
3128     # We are inside a <programlisting> but outside any CDATA sections,
3129     # so we expand any gtk-doc abbreviations.
3130     # FIXME: why is this different from &ExpandAbbreviationsCallback(),
3131     #        why not just call it
3132     $text =~ s/#(\w+)/&MakeHashXRef($1, "");/eg;
3133   } elsif ($tag eq "<![CDATA[") {
3134     # NOTE: this is a fallback. It is normally done by the Markdown parser.
3135     $text = &ReplaceEntities ($text, $symbol);
3136   }
3138   return $text;
3141 sub MakeHashXRef {
3142     my ($symbol, $tag) = @_;;
3143     my $text = $symbol;
3145     # Check for things like '#include', '#define', and skip them.
3146     if ($PreProcessorDirectives{$symbol}) {
3147       return "#$symbol";
3148     }
3150     # Get rid of special suffixes ('-struct','-enum').
3151     $text =~ s/-struct$//;
3152     $text =~ s/-enum$//;
3154     # If the symbol is in the form "Object::signal", then change the symbol to
3155     # "Object-signal" and use "signal" as the text.
3156     if ($symbol =~ s/::/-/) {
3157       $text = "“$'”";
3158     }
3160     # If the symbol is in the form "Object:property", then change the symbol to
3161     # "Object--property" and use "property" as the text.
3162     if ($symbol =~ s/:/--/) {
3163       $text = "“$'”";
3164     }
3166     if ($tag ne "") {
3167       $text = tagify ($text, $tag);
3168     }
3170     return &MakeXRef($symbol, $text);
3174 #############################################################################
3175 # Function    : ModifyXMLElements
3176 # Description : Looks for given XML element tags within the text, and calls
3177 #               the callback on pieces of text inside & outside those elements.
3178 #               Used for special handling of text inside things like CDATA
3179 #               and <programlisting>.
3180 # Arguments   : $text - the text.
3181 #               $symbol - the symbol currently being documented (only used for
3182 #                      error messages).
3183 #               $start_tag_regexp - the regular expression to match start tags.
3184 #                      e.g. "<!\\[CDATA\\[|<programlisting[^>]*>" to match
3185 #                      CDATA sections or programlisting elements.
3186 #               $end_tag_func - function which is passed the matched start tag
3187 #                      and should return the appropriate end tag string regexp.
3188 #               $callback - callback called with each part of the text. It is
3189 #                      called with a piece of text, the symbol being
3190 #                      documented, and the matched start tag or "" if the text
3191 #                      is outside the XML elements being matched.
3192 #############################################################################
3193 sub ModifyXMLElements {
3194     my ($text, $symbol, $start_tag_regexp, $end_tag_func, $callback) = @_;
3195     my ($before_tag, $start_tag, $end_tag_regexp, $end_tag);
3196     my $result = "";
3198     while ($text =~ m/$start_tag_regexp/s) {
3199       $before_tag = $`; # Prematch for last successful match string
3200       $start_tag = $&;  # Last successful match
3201       $text = $';       # Postmatch for last successful match string
3203       $result .= &$callback ($before_tag, $symbol, "");
3204       $result .= $start_tag;
3206       # get the matching end-tag for current tag
3207       $end_tag_regexp = &$end_tag_func ($start_tag);
3209       if ($text =~ m/$end_tag_regexp/s) {
3210         $before_tag = $`;
3211         $end_tag = $&;
3212         $text = $';
3214         $result .= &$callback ($before_tag, $symbol, $start_tag);
3215         $result .= $end_tag;
3216       } else {
3217         &LogWarning (&GetSymbolSourceFile ($symbol), &GetSymbolSourceLine($symbol),
3218             "Can't find tag end: $end_tag_regexp in docs for: $symbol.");
3219         # Just assume it is all inside the tag.
3220         $result .= &$callback ($text, $symbol, $start_tag);
3221         $text = "";
3222       }
3223     }
3225     # Handle any remaining text outside the tags.
3226     $result .= &$callback ($text, $symbol, "");
3228     return $result;
3231 sub noop {
3232   return $_[0];
3235 # Adds a tag around some text.
3236 # e.g tagify("Text", "literal") => "<literal>Text</literal>".
3237 sub tagify {
3238    my ($text, $elem) = @_;
3239    return "<" . $elem . ">" . $text . "</" . $elem . ">";
3242 #############################################################################
3243 # Function    : MakeDocHeader
3244 # Description : Builds a docbook header for the given tag
3245 # Arguments   : $tag - doctype tag
3246 #############################################################################
3248 sub MakeDocHeader {
3249     my ($tag) = @_;
3250     my $header = $doctype_header;
3251     $header =~ s/<!DOCTYPE \w+/<!DOCTYPE $tag/;
3253     # fix the path for book since this is one level up
3254     if ($tag eq "book") {
3255         $header =~ s#<!ENTITY % gtkdocentities SYSTEM \"../([a-zA-Z./]+)\">#<!ENTITY % gtkdocentities SYSTEM \"$1\">#;
3256     }
3258     return $header;
3262 #############################################################################
3263 # Function    : MakeXRef
3264 # Description : This returns a cross-reference link to the given symbol.
3265 #                Though it doesn't try to do this for a few standard C types
3266 #                that it        knows won't be in the documentation.
3267 # Arguments   : $symbol - the symbol to try to create a XRef to.
3268 #               $text - text text to put inside the XRef, defaults to $symbol
3269 #############################################################################
3271 sub MakeXRef {
3272     my ($symbol, $text) = ($_[0], $_[1]);
3274     $symbol =~ s/^\s+//;
3275     $symbol =~ s/\s+$//;
3277     if (!defined($text)) {
3278         $text = $symbol;
3280         # Get rid of special suffixes ('-struct','-enum').
3281         $text =~ s/-struct$//;
3282         $text =~ s/-enum$//;
3283     }
3285     if ($symbol =~ m/ /) {
3286         return "$text";
3287     }
3289     @TRACE@("Getting type link for $symbol -> $text\n");
3291     my $symbol_id = &CreateValidSGMLID ($symbol);
3292     return "<link linkend=\"$symbol_id\">$text</link>";
3296 #############################################################################
3297 # Function    : MakeIndexterms
3298 # Description : This returns a indexterm elements for the given symbol
3299 # Arguments   : $symbol - the symbol to create indexterms for
3300 #############################################################################
3302 sub MakeIndexterms {
3303   my ($symbol, $id) = @_;
3304   my $terms =  "";
3305   my $sortas = "";
3307   # make the index useful, by ommiting the namespace when sorting
3308   if ($NAME_SPACE ne "") {
3309     if ($symbol =~ m/^$NAME_SPACE\_?(.*)/i) {
3310        $sortas=" sortas=\"$1\"";
3311     }
3312   }
3314   if (exists $Deprecated{$symbol}) {
3315       $terms .= "<indexterm zone=\"$id\" role=\"deprecated\"><primary$sortas>$symbol</primary></indexterm>";
3316       $IndexEntriesDeprecated{$symbol}=$id;
3317       $IndexEntriesFull{$symbol}=$id;
3318   }
3319   if (exists $Since{$symbol}) {
3320      my $since = $Since{$symbol};
3321      $since =~ s/^\s+//;
3322      $since =~ s/\s+$//;
3323      if ($since ne "") {
3324          $terms .= "<indexterm zone=\"$id\" role=\"$since\"><primary$sortas>$symbol</primary></indexterm>";
3325      }
3326      $IndexEntriesSince{$symbol}=$id;
3327      $IndexEntriesFull{$symbol}=$id;
3328   }
3329   if ($terms eq "") {
3330      $terms .= "<indexterm zone=\"$id\"><primary$sortas>$symbol</primary></indexterm>";
3331      $IndexEntriesFull{$symbol}=$id;
3332   }
3334   return $terms;
3337 #############################################################################
3338 # Function    : MakeDeprecationNote
3339 # Description : This returns a deprecation warning for the given symbol.
3340 # Arguments   : $symbol - the symbol to try to create a warning for.
3341 #############################################################################
3343 sub MakeDeprecationNote {
3344     my ($symbol) = $_[0];
3345     my $desc = "";
3346     if (exists $Deprecated{$symbol}) {
3347         my $note;
3349         $desc .= "<warning><para><literal>$symbol</literal> ";
3351         $note = $Deprecated{$symbol};
3353         if ($note =~ /^\s*([0-9\.]+)\s*:?/) {
3354                 $desc .= "has been deprecated since version $1 and should not be used in newly-written code.</para>";
3355         } else {
3356                 $desc .= "is deprecated and should not be used in newly-written code.</para>";
3357         }
3358         $note =~ s/^\s*([0-9\.]+)\s*:?\s*//;
3359         $note =~ s/^\s+//;
3360         $note =~ s/\s+$//;
3361         if ($note ne "") {
3362             $note = &ConvertMarkDown($symbol, $note);
3363             $desc .= " " . $note;
3364         }
3365         $desc .= "</warning>\n";
3366     }
3367     return $desc;
3370 #############################################################################
3371 # Function    : MakeConditionDescription
3372 # Description : This returns a sumary of conditions for the given symbol.
3373 # Arguments   : $symbol - the symbol to try to create the sumary.
3374 #############################################################################
3376 sub MakeConditionDescription {
3377     my ($symbol) = $_[0];
3378     my $desc = "";
3380     if (exists $Deprecated{$symbol}) {
3381         if ($desc ne "") {
3382             $desc .= "|";
3383         }
3385         if ($Deprecated{$symbol} =~ /^\s*(.*?)\s*$/) {
3386                 $desc .= "deprecated:$1";
3387         } else {
3388                 $desc .= "deprecated";
3389         }
3390     }
3392     if (exists $Since{$symbol}) {
3393         if ($desc ne "") {
3394             $desc .= "|";
3395         }
3397         if ($Since{$symbol} =~ /^\s*(.*?)\s*$/) {
3398                 $desc .= "since:$1";
3399         } else {
3400                 $desc .= "since";
3401         }
3402     }
3404     if (exists $StabilityLevel{$symbol}) {
3405         if ($desc ne "") {
3406             $desc .= "|";
3407         }
3408         $desc .= "stability:".$StabilityLevel{$symbol};
3409     }
3411     if ($desc ne "") {
3412         my $cond = $desc;
3413         $cond =~ s/\"/&quot;/g;
3414         $desc=" condition=\"".$cond."\"";
3415         @TRACE@("condition for '$symbol' = '$desc'\n");
3416     }
3417     return $desc;
3420 #############################################################################
3421 # Function    : GetHierarchy
3422 # Description : Returns the DocBook output describing the ancestors and
3423 #               immediate children of a GObject subclass. It uses the
3424 #               global @Objects and @ObjectLevels arrays to walk the tree.
3426 # Arguments   : $object - the GtkObject subclass.
3427 #               @hierarchy - previous hierarchy
3428 #############################################################################
3430 sub GetHierarchy {
3431     my ($object,$hierarchy_ref) = @_;
3432     my @hierarchy = @{$hierarchy_ref};
3434     # Find object in the objects array.
3435     my $found = 0;
3436     my @children = ();
3437     my $i;
3438     my $level;
3439     my $j;
3440     for ($i = 0; $i < @Objects; $i++) {
3441         if ($found) {
3442             if ($ObjectLevels[$i] <= $level) {
3443             last;
3444         }
3445             elsif ($ObjectLevels[$i] == $level + 1) {
3446                 push (@children, $Objects[$i]);
3447             }
3448         }
3449         elsif ($Objects[$i] eq $object) {
3450             $found = 1;
3451             $j = $i;
3452             $level = $ObjectLevels[$i];
3453         }
3454     }
3455     if (!$found) {
3456         return @hierarchy;
3457     }
3459     # Walk up the hierarchy, pushing ancestors onto the ancestors array.
3460     my @ancestors = ();
3461     push (@ancestors, $object);
3462     @TRACE@("Level: $level\n");
3463     while ($level > 1) {
3464         $j--;
3465         if ($ObjectLevels[$j] < $level) {
3466             push (@ancestors, $Objects[$j]);
3467             $level = $ObjectLevels[$j];
3468             @TRACE@("Level: $level\n");
3469         }
3470     }
3472     # Output the ancestors, indented and with links.
3473     my $last_index = 0;
3474     $level = 1;
3475     for ($i = $#ancestors; $i >= 0; $i--) {
3476         my $entry_text;
3477         my $alt_text;
3478         my $ancestor = $ancestors[$i];
3479         my $ancestor_id = &CreateValidSGMLID ($ancestor);
3480         my $indent = ' ' x ($level * 4);
3481         # Don't add a link to the current object, i.e. when i == 0.
3482         if ($i > 0) {
3483             $entry_text = $indent . "<link linkend=\"$ancestor_id\">$ancestor</link>";
3484             $alt_text = $indent . $ancestor;
3485         } else {
3486             $entry_text = $indent . $ancestor;
3487             $alt_text = $indent . "<link linkend=\"$ancestor_id\">$ancestor</link>";
3488         }
3489         @TRACE@("Checking for '$entry_text' or '$alt_text'");
3490         # Check if we already have this object
3491         my $index = -1;
3492         for ($j = 0; $j <= $#hierarchy; $j++) {
3493             if (($hierarchy[$j] eq $entry_text) or ($hierarchy[$j] eq $alt_text)) {
3494                 $index = $j;
3495                 last;
3496             }
3497         }
3498         if ($index == -1) {
3499             # We have a new entry, find insert position in alphabetical order
3500             my $found = 0;
3501             for ($j = $last_index; $j <= $#hierarchy; $j++) {
3502                 if ($hierarchy[$j] !~ m/^${indent}/) {
3503                     $last_index = $j;
3504                     $found = 1;
3505                     last;
3506                 } elsif ($hierarchy[$j] =~ m/^${indent}[^ ]/) {
3507                     my $stripped_text = $hierarchy[$j];
3508                     if ($entry_text !~ m/<link linkend/) {
3509                         $stripped_text =~ s%<link linkend="[A-Za-z]*">%%;
3510                         $stripped_text =~ s%</link>%%;
3511                     }
3512                     if ($entry_text lt $stripped_text) {
3513                         $last_index = $j;
3514                         $found = 1;
3515                         last;
3516                     }
3517                 }
3518             }
3519             # Append to bottom
3520             if (!$found) {
3521               $last_index = 1 + $#hierarchy;
3522             }
3523             splice @hierarchy, $last_index, 0, ($entry_text);
3524             $last_index++;
3525         } else {
3526             # Already have this one, make sure we use the not linked version
3527             if ($entry_text !~ m/<link linkend=/) {
3528               $hierarchy[$j] = $entry_text;
3529             }
3530             # Remember index as base insert point
3531             $last_index = $index + 1;
3532         }
3533         $level++;
3534     }
3535     # Output the children, indented and with links.
3536     for ($i = 0; $i <= $#children; $i++) {
3537         my $id = &CreateValidSGMLID ($children[$i]);
3538         my $indented_text = ' ' x ($level * 4) . "<link linkend=\"$id\">$children[$i]</link>";
3539         splice @hierarchy, $last_index, 0, ($indented_text);
3540         $last_index++;
3541     }
3543     return @hierarchy;
3546 #############################################################################
3547 # Function    : GetInterfaces
3548 # Description : Returns the DocBook output describing the interfaces
3549 #               implemented by a class. It uses the global %Interfaces hash.
3550 # Arguments   : $object - the GtkObject subclass.
3551 #############################################################################
3553 sub GetInterfaces {
3554     my ($object) = @_;
3555     my $text = "";
3556     my $i;
3558     # Find object in the objects array.
3559     if (exists($Interfaces{$object})) {
3560         my @ifaces = split(' ', $Interfaces{$object});
3561         $text = <<EOF;
3562 <para>
3563 $object implements
3565         for ($i = 0; $i <= $#ifaces; $i++) {
3566             my $id = &CreateValidSGMLID ($ifaces[$i]);
3567             $text .= " <link linkend=\"$id\">$ifaces[$i]</link>";
3568             if ($i < $#ifaces - 1) {
3569                 $text .= ', ';
3570             }
3571             elsif ($i < $#ifaces) {
3572                 $text .= ' and ';
3573             }
3574             else {
3575                 $text .= '.';
3576             }
3577         }
3578         $text .= <<EOF;
3579 </para>
3581     }
3583     return $text;
3586 #############################################################################
3587 # Function    : GetImplementations
3588 # Description : Returns the DocBook output describing the implementations
3589 #               of an interface. It uses the global %Interfaces hash.
3590 # Arguments   : $object - the GtkObject subclass.
3591 #############################################################################
3593 sub GetImplementations {
3594     my ($object) = @_;
3595     my @impls = ();
3596     my $text = "";
3597     my $i;
3598     foreach my $key (keys %Interfaces) {
3599         if ($Interfaces{$key} =~ /\b$object\b/) {
3600             push (@impls, $key);
3601         }
3602     }
3603     if ($#impls >= 0) {
3604         @impls = sort @impls;
3605         $text = <<EOF;
3606 <para>
3607 $object is implemented by
3609         for ($i = 0; $i <= $#impls; $i++) {
3610             my $id = &CreateValidSGMLID ($impls[$i]);
3611             $text .= " <link linkend=\"$id\">$impls[$i]</link>";
3612             if ($i < $#impls - 1) {
3613                 $text .= ', ';
3614             }
3615             elsif ($i < $#impls) {
3616                 $text .= ' and ';
3617             }
3618             else {
3619                 $text .= '.';
3620             }
3621         }
3622         $text .= <<EOF;
3623 </para>
3625     }
3626     return $text;
3630 #############################################################################
3631 # Function    : GetPrerequisites
3632 # Description : Returns the DocBook output describing the prerequisites
3633 #               of an interface. It uses the global %Prerequisites hash.
3634 # Arguments   : $iface - the interface.
3635 #############################################################################
3637 sub GetPrerequisites {
3638     my ($iface) = @_;
3639     my $text = "";
3640     my $i;
3642     if (exists($Prerequisites{$iface})) {
3643         $text = <<EOF;
3644 <para>
3645 $iface requires
3647         my @prereqs = split(' ', $Prerequisites{$iface});
3648         for ($i = 0; $i <= $#prereqs; $i++) {
3649             my $id = &CreateValidSGMLID ($prereqs[$i]);
3650             $text .= " <link linkend=\"$id\">$prereqs[$i]</link>";
3651             if ($i < $#prereqs - 1) {
3652                 $text .= ', ';
3653             }
3654             elsif ($i < $#prereqs) {
3655                 $text .= ' and ';
3656             }
3657             else {
3658                 $text .= '.';
3659             }
3660         }
3661         $text .= <<EOF;
3662 </para>
3664     }
3665     return $text;
3668 #############################################################################
3669 # Function    : GetDerived
3670 # Description : Returns the DocBook output describing the derived interfaces
3671 #               of an interface. It uses the global %Prerequisites hash.
3672 # Arguments   : $iface - the interface.
3673 #############################################################################
3675 sub GetDerived {
3676     my ($iface) = @_;
3677     my $text = "";
3678     my $i;
3680     my @derived = ();
3681     foreach my $key (keys %Prerequisites) {
3682         if ($Prerequisites{$key} =~ /\b$iface\b/) {
3683             push (@derived, $key);
3684         }
3685     }
3686     if ($#derived >= 0) {
3687         @derived = sort @derived;
3688         $text = <<EOF;
3689 <para>
3690 $iface is required by
3692         for ($i = 0; $i <= $#derived; $i++) {
3693             my $id = &CreateValidSGMLID ($derived[$i]);
3694             $text .= " <link linkend=\"$id\">$derived[$i]</link>";
3695             if ($i < $#derived - 1) {
3696                 $text .= ', ';
3697             }
3698             elsif ($i < $#derived) {
3699                 $text .= ' and ';
3700             }
3701             else {
3702                 $text .= '.';
3703             }
3704         }
3705         $text .= <<EOF;
3706 </para>
3708     }
3709     return $text;
3713 #############################################################################
3714 # Function    : GetSignals
3715 # Description : Returns the synopsis and detailed description DocBook output
3716 #                for the signal handlers of a given GtkObject subclass.
3717 # Arguments   : $object - the GtkObject subclass, e.g. 'GtkButton'.
3718 #############################################################################
3720 sub GetSignals {
3721     my ($object) = @_;
3722     my $synop = "";
3723     my $desc = "";
3725     my $i;
3726     for ($i = 0; $i <= $#SignalObjects; $i++) {
3727         if ($SignalObjects[$i] eq $object) {
3728             @TRACE@("Found signal: $SignalNames[$i]\n");
3729             my $name = $SignalNames[$i];
3730             my $symbol = "${object}::${name}";
3731             my $id = &CreateValidSGMLID ("$object-$name");
3733             $desc .= "<refsect2 id=\"$id\" role=\"signal\"><title>The <literal>“$name”</literal> signal</title>\n";
3734             $desc .= MakeIndexterms($symbol, $id);
3735             $desc .= "\n";
3736             $desc .= OutputSymbolExtraLinks($symbol);
3738             $desc .= "<programlisting language=\"C\">";
3740             $SignalReturns[$i] =~ m/\s*(const\s+)?(\w+)\s*(\**)/;
3741             my $type_modifier = defined($1) ? $1 : "";
3742             my $type = $2;
3743             my $pointer = $3;
3744             my $xref = &MakeXRef ($type, &tagify($type, "returnvalue"));
3746             my $ret_type_output = "$type_modifier$xref$pointer";
3747             my $callback_name = "user_function";
3748             $desc  .= "${ret_type_output}\n${callback_name} (";
3750             my $indentation = ' ' x (length($callback_name) + 2);
3751             my $pad = $indentation;
3753             my $sourceparams = $SourceSymbolParams{$symbol};
3754             my @params = split ("\n", $SignalPrototypes[$i]);
3755             my $j;
3756             my $l;
3757             my $type_len = length("gpointer");
3758             my $name_len = length("user_data");
3759             # do two passes, the first one is to calculate padding
3760             for ($l = 0; $l < 2; $l++) {
3761                 for ($j = 0; $j <= $#params; $j++) {
3762                     my $param_name;
3763                     # allow alphanumerics, '_', '[' & ']' in param names
3764                     if ($params[$j] =~ m/^\s*(\w+)\s*(\**)\s*([\w\[\]]+)\s*$/) {
3765                         $type = $1;
3766                         $pointer = $2;
3767                         if (defined($sourceparams)) {
3768                             $param_name = $$sourceparams[$PARAM_FIELD_COUNT * $j];
3769                         }
3770                         else {
3771                             $param_name = $3;
3772                         }
3773                         if (!defined($param_name)) {
3774                             $param_name = "arg$j";
3775                         }
3776                         if ($l == 0) {
3777                             if (length($type) + length($pointer) > $type_len) {
3778                                 $type_len = length($type) + length($pointer);
3779                             }
3780                             if (length($param_name) > $name_len) {
3781                                 $name_len = length($param_name);
3782                             }
3783                         }
3784                         else {
3785                             $xref = &MakeXRef ($type, &tagify($type, "type"));
3786                             $pad = ' ' x ($type_len - length($type) - length($pointer));
3787                             $desc .= "$xref$pad $pointer${param_name},\n";
3788                             $desc .= $indentation;
3789                         }
3790                     } else {
3791                         &LogWarning (&GetSymbolSourceFile ($symbol), &GetSymbolSourceLine($symbol),
3792                              "Can't parse arg: $params[$j]\nArgs:$SignalPrototypes[$i]");
3793                     }
3794                 }
3795             }
3796             $xref = &MakeXRef ("gpointer", &tagify("gpointer", "type"));
3797             $pad = ' ' x ($type_len - length("gpointer"));
3798             $desc  .= "$xref$pad user_data)";
3799             $desc  .= "</programlisting>\n";
3801             my $flags = $SignalFlags[$i];
3802             my $flags_string = "";
3804             if (defined ($flags)) {
3805               if ($flags =~ m/f/) {
3806                 $flags_string = "<link linkend=\"G-SIGNAL-RUN-FIRST:CAPS\">Run First</link>";
3807               }
3808               elsif ($flags =~ m/l/) {
3809                 $flags_string = "<link linkend=\"G-SIGNAL-RUN-LAST:CAPS\">Run Last</link>";
3810               }
3811               elsif ($flags =~ m/c/) {
3812                 $flags_string = "<link linkend=\"G-SIGNAL-RUN-CLEANUP:CAPS\">Cleanup</link>";
3813                 $flags_string = "Cleanup";
3814               }
3815               if ($flags =~ m/r/) {
3816                 if ($flags_string) { $flags_string .= " / "; }
3817                 $flags_string = "<link linkend=\"G-SIGNAL-NO-RECURSE:CAPS\">No Recursion</link>";
3818               }
3819               if ($flags =~ m/d/) {
3820                 if ($flags_string) { $flags_string .= " / "; }
3821                 $flags_string = "<link linkend=\"G-SIGNAL-DETAILED:CAPS\">Has Details</link>";
3822               }
3823               if ($flags =~ m/a/) {
3824                 if ($flags_string) { $flags_string .= " / "; }
3825                 $flags_string = "<link linkend=\"G-SIGNAL-ACTION:CAPS\">Action</link>";
3826               }
3827               if ($flags =~ m/h/) {
3828                 if ($flags_string) { $flags_string .= " / "; }
3829                 $flags_string = "<link linkend=\"G-SIGNAL-NO-HOOKS:CAPS\">No Hooks</link>";
3830               }
3831             }
3833             $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";
3835             my $parameters = &OutputParamDescriptions ("SIGNAL", $symbol);
3837             $AllSymbols{$symbol} = 1;
3838             if (defined ($SymbolDocs{$symbol})) {
3839                 my $symbol_docs = &ConvertMarkDown($symbol, $SymbolDocs{$symbol});
3841                 $desc .= $symbol_docs;
3843                 if (!IsEmptyDoc($SymbolDocs{$symbol})) {
3844                     $AllDocumentedSymbols{$symbol} = 1;
3845                 }
3846             }
3847             if (defined ($SymbolAnnotations{$symbol})) {
3848                 my $param_desc = $SymbolAnnotations{$symbol};
3849                 my $param_annotations = "";
3850                 ($param_desc,$param_annotations) = &ExpandAnnotation($symbol, $param_desc);
3851                 if ($param_annotations ne "") {
3852                     $desc .= "\n<para>$param_annotations</para>";
3853                 }
3854             }
3855             $desc .= &MakeDeprecationNote($symbol);
3857             $desc .= $parameters;
3858             if ($flags_string) {
3859                 $desc  .= "<para>Flags: $flags_string</para>\n";
3860             }
3861             $desc .= OutputSymbolTraits ($symbol);
3862             $desc .= "</refsect2>";
3863         }
3864     }
3865     return ($synop, $desc);
3869 #############################################################################
3870 # Function    : GetArgs
3871 # Description : Returns the synopsis and detailed description DocBook output
3872 #                for the Args of a given GtkObject subclass.
3873 # Arguments   : $object - the GtkObject subclass, e.g. 'GtkButton'.
3874 #############################################################################
3876 sub GetArgs {
3877     my ($object) = @_;
3878     my $synop = "";
3879     my $desc = "";
3880     my $child_synop = "";
3881     my $child_desc = "";
3882     my $style_synop = "";
3883     my $style_desc = "";
3885     my $i;
3886     for ($i = 0; $i <= $#ArgObjects; $i++) {
3887         if ($ArgObjects[$i] eq $object) {
3888             @TRACE@("Found arg: $ArgNames[$i]\n");
3889             my $name = $ArgNames[$i];
3890             my $flags = $ArgFlags[$i];
3891             my $flags_string = "";
3892             my $kind = "";
3893             my $id_sep = "";
3895             if ($flags =~ m/c/) {
3896                 $kind = "child property";
3897                 $id_sep = "c-";
3898             }
3899             elsif ($flags =~ m/s/) {
3900                 $kind = "style property";
3901                 $id_sep = "s-";
3902             }
3903             else {
3904                 $kind = "property";
3905             }
3907             # Remember only one colon so we don't clash with signals.
3908             my $symbol = "${object}:${name}";
3909             # use two dashes and ev. an extra separator here for the same reason.
3910             my $id = &CreateValidSGMLID ("$object--$id_sep$name");
3912             my $type = $ArgTypes[$i];
3913             my $type_output;
3914             my $range = $ArgRanges[$i];
3915             my $range_output = CreateValidSGML ($range);
3916             my $default = $ArgDefaults[$i];
3917             my $default_output = CreateValidSGML ($default);
3919             if ($type eq "GtkString") {
3920                 $type = "char&#160;*";
3921             }
3922             if ($type eq "GtkSignal") {
3923                 $type = "GtkSignalFunc, gpointer";
3924                 $type_output = &MakeXRef ("GtkSignalFunc") . ", "
3925                     . &MakeXRef ("gpointer");
3926             } elsif ($type =~ m/^(\w+)\*$/) {
3927                 $type_output = &MakeXRef ($1, &tagify($1, "type")) . "&#160;*";
3928             } else {
3929                 $type_output = &MakeXRef ($type, &tagify($type, "type"));
3930             }
3932             if ($flags =~ m/r/) {
3933                 $flags_string = "Read";
3934             }
3935             if ($flags =~ m/w/) {
3936                 if ($flags_string) { $flags_string .= " / "; }
3937                 $flags_string .= "Write";
3938             }
3939             if ($flags =~ m/x/) {
3940                 if ($flags_string) { $flags_string .= " / "; }
3941                 $flags_string .= "Construct";
3942             }
3943             if ($flags =~ m/X/) {
3944                 if ($flags_string) { $flags_string .= " / "; }
3945                 $flags_string .= "Construct Only";
3946             }
3948             $AllSymbols{$symbol} = 1;
3949             my $blurb = "";
3950             if (defined($SymbolDocs{$symbol}) &&
3951                 !IsEmptyDoc($SymbolDocs{$symbol})) {
3952                 $blurb = &ConvertMarkDown($symbol, $SymbolDocs{$symbol});
3953                 @TRACE@(".. [$SymbolDocs{$symbol}][$blurb]\n");
3954                 $AllDocumentedSymbols{$symbol} = 1;
3955             }
3956             else {
3957                 if ($ArgBlurbs[$i] ne "") {
3958                     $blurb = "<para>" . &CreateValidSGML ($ArgBlurbs[$i]) . "</para>";
3959                     $AllDocumentedSymbols{$symbol} = 1;
3960                 } else {
3961                     # FIXME: print a warning?
3962                     @TRACE@(".. no description\n");
3963                 }
3964             }
3966             my $pad1 = " " x (24 - length ($name));
3968             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";
3969             my $arg_desc = "<refsect2 id=\"$id\" role=\"property\"><title>The <literal>“$name”</literal> $kind</title>\n";
3970             $arg_desc .= MakeIndexterms($symbol, $id);
3971             $arg_desc .= "\n";
3972             $arg_desc .= OutputSymbolExtraLinks($symbol);
3974             $arg_desc .= "<programlisting>  “$name”$pad1 $type_output</programlisting>\n";
3975             $arg_desc .= $blurb;
3976             if (defined ($SymbolAnnotations{$symbol})) {
3977                 my $param_desc = $SymbolAnnotations{$symbol};
3978                 my $param_annotations = "";
3979                 ($param_desc,$param_annotations) = &ExpandAnnotation($symbol, $param_desc);
3980                 if ($param_annotations ne "") {
3981                     $arg_desc .= "\n<para>$param_annotations</para>";
3982                 }
3983             }
3984             $arg_desc .= &MakeDeprecationNote($symbol);
3986             if ($flags_string) {
3987               $arg_desc  .= "<para>Flags: $flags_string</para>\n";
3988             }
3989             if ($range ne "") {
3990                 $arg_desc .= "<para>Allowed values: $range_output</para>\n";
3991             }
3992             if ($default ne "") {
3993                 $arg_desc .= "<para>Default value: $default_output</para>\n";
3994             }
3995             $arg_desc .= OutputSymbolTraits ($symbol);
3996             $arg_desc .= "</refsect2>\n";
3998             if ($flags =~ m/c/) {
3999                 $child_synop .= $arg_synop;
4000                 $child_desc .= $arg_desc;
4001             }
4002             elsif ($flags =~ m/s/) {
4003                 $style_synop .= $arg_synop;
4004                 $style_desc .= $arg_desc;
4005             }
4006             else {
4007                 $synop .= $arg_synop;
4008                 $desc .= $arg_desc;
4009             }
4010         }
4011     }
4012     return ($synop, $child_synop, $style_synop, $desc, $child_desc, $style_desc);
4016 #############################################################################
4017 # Function    : ReadSourceDocumentation
4018 # Description : This reads in the documentation embedded in comment blocks
4019 #                in the source code (for Gnome).
4021 #                Parameter descriptions override any in the template files.
4022 #                Function descriptions are placed before any description from
4023 #                the template files.
4025 #                It recursively descends the source directory looking for .c
4026 #                files and scans them looking for specially-formatted comment
4027 #                blocks.
4029 # Arguments   : $source_dir - the directory to scan.
4030 #############m###############################################################
4032 sub ReadSourceDocumentation {
4033     my ($source_dir) = @_;
4034     my ($file, $dir, @suffix_list, $suffix);
4036     # prepend entries from @SOURCE_DIR
4037     for my $dir (@SOURCE_DIRS) {
4038         # Check if the filename is in the ignore list.
4039         if ($source_dir =~ m%^\Q$dir\E/(.*)$% and $IGNORE_FILES =~ m/(\s|^)\Q$1\E(\s|$)/) {
4040             @TRACE@("Skipping source directory: $source_dir");
4041             return;
4042         } else {
4043             @TRACE@("No match for: ".($1 || $source_dir));
4044         }
4045     }
4047     @TRACE@("Scanning source directory: $source_dir");
4049     # This array holds any subdirectories found.
4050     my (@subdirs) = ();
4052     @suffix_list = split (/,/, $SOURCE_SUFFIXES);
4054     opendir (SRCDIR, $source_dir)
4055         || die "Can't open source directory $source_dir: $!";
4057     foreach $file (readdir (SRCDIR)) {
4058       if ($file =~ /^\./) {
4059         next;
4060       } elsif (-d "$source_dir/$file") {
4061         push (@subdirs, $file);
4062       } elsif (@suffix_list) {
4063         foreach $suffix (@suffix_list) {
4064           if ($file =~ m/\.\Q${suffix}\E$/) {
4065             &ScanSourceFile ("$source_dir/$file");
4066           }
4067         }
4068       } elsif ($file =~ m/\.[ch]$/) {
4069         &ScanSourceFile ("$source_dir/$file");
4070       }
4071     }
4072     closedir (SRCDIR);
4074     # Now recursively scan the subdirectories.
4075     foreach $dir (@subdirs) {
4076         &ReadSourceDocumentation ("$source_dir/$dir");
4077     }
4081 #############################################################################
4082 # Function    : ScanSourceFile
4083 # Description : Scans one source file looking for specially-formatted comment
4084 #                blocks. Later &MergeSourceDocumentation is used to merge any
4085 #                documentation found with the documentation already read in
4086 #                from the template files.
4088 # Arguments   : $file - the file to scan.
4089 #############################################################################
4091 sub ScanSourceFile {
4092     my ($file) = @_;
4093     my $basename;
4095     # prepend entries from @SOURCE_DIR
4096     for my $dir (@SOURCE_DIRS) {
4097         # Check if the filename is in the ignore list.
4098         if ($file =~ m%^\Q$dir\E/(.*)$% and $IGNORE_FILES =~ m/(\s|^)\Q$1\E(\s|$)/) {
4099             @TRACE@("Skipping source file: $file");
4100             return;
4101         }
4102     }
4104     if ($file =~ m/^.*[\/\\]([^\/\\]*)$/) {
4105         $basename = $1;
4106     } else {
4107         &LogWarning ($file, 1, "Can't find basename for this filename.");
4108         $basename = $file;
4109     }
4111     # Check if the basename is in the list of files to ignore.
4112     if ($IGNORE_FILES =~ m/(\s|^)\Q${basename}\E(\s|$)/) {
4113         @TRACE@("Skipping source file: $file");
4114         return;
4115     }
4117     @TRACE@("Scanning source file: $file");
4119     open (SRCFILE, $file)
4120         || die "Can't open $file: $!";
4121     my $in_comment_block = 0;
4122     my $symbol;
4123     my $in_part = "";
4124     my ($description, $return_desc);
4125     my ($since_desc, $stability_desc, $deprecated_desc);
4126     my $current_param;
4127     my @params;
4128     while (<SRCFILE>) {
4129         # Look for the start of a comment block.
4130         if (!$in_comment_block) {
4131             if (m%^\s*/\*.*\*/%) {
4132                 #one-line comment - not gtkdoc
4133             } elsif (m%^\s*/\*\*\s%) {
4134                 @TRACE@("Found comment block start\n");
4136                 $in_comment_block = 1;
4138                 # Reset all the symbol data.
4139                 $symbol = "";
4140                 $in_part = "";
4141                 $description = "";
4142                 $return_desc = "";
4143                 $since_desc = "";
4144                 $deprecated_desc = "";
4145                 $stability_desc = "";
4146                 $current_param = -1;
4147                 @params = ();
4148             }
4149             next;
4150         }
4152         # We're in a comment block. Check if we've found the end of it.
4153         if (m%^\s*\*+/%) {
4154             if (!$symbol) {
4155                 # maybe its not even meant to be a gtk-doc comment?
4156                 &LogWarning ($file, $., "Symbol name not found at the start of the comment block.");
4157             } else {
4158                 # Add the return value description onto the end of the params.
4159                 if ($return_desc) {
4160                     # TODO(ensonic): check for duplicated Return docs
4161                     # &LogWarning ($file, $., "Multiple Returns for $symbol.");
4162                     push (@params, "Returns");
4163                     push (@params, $return_desc);
4164                 }
4165                 # Convert special characters
4166                 $description = &ConvertSGMLChars ($symbol, $description);
4167                 my $k;
4168                 for ($k = 1; $k <= $#params; $k += $PARAM_FIELD_COUNT) {
4169                     $params[$k] = &ConvertSGMLChars ($symbol, $params[$k]);
4170                 }
4172                 # Handle Section docs
4173                 if ($symbol =~ m/SECTION:\s*(.*)/) {
4174                     my $real_symbol=$1;
4175                     my $key;
4177                     if (scalar %KnownSymbols) {
4178                         if ((! defined($KnownSymbols{"$TMPL_DIR/$real_symbol:Long_Description"})) || $KnownSymbols{"$TMPL_DIR/$real_symbol:Long_Description"} != 1) {
4179                             &LogWarning ($file, $., "Section $real_symbol is not defined in the $MODULE-sections.txt file.");
4180                         }
4181                     }
4183                     @TRACE@("SECTION DOCS found in source for : '$real_symbol'\n");
4184                     for ($k = 0; $k <= $#params; $k += $PARAM_FIELD_COUNT) {
4185                         @TRACE@("   '".$params[$k]."'\n");
4186                         $params[$k] = "\L$params[$k]";
4187                         undef $key;
4188                         if ($params[$k] eq "short_description") {
4189                             $key = "$TMPL_DIR/$real_symbol:Short_Description";
4190                         } elsif ($params[$k] eq "see_also") {
4191                             $key = "$TMPL_DIR/$real_symbol:See_Also";
4192                         } elsif ($params[$k] eq "title") {
4193                             $key = "$TMPL_DIR/$real_symbol:Title";
4194                         } elsif ($params[$k] eq "stability") {
4195                             $key = "$TMPL_DIR/$real_symbol:Stability_Level";
4196                         } elsif ($params[$k] eq "section_id") {
4197                             $key = "$TMPL_DIR/$real_symbol:Section_Id";
4198                         } elsif ($params[$k] eq "include") {
4199                             $key = "$TMPL_DIR/$real_symbol:Include";
4200                         } elsif ($params[$k] eq "image") {
4201                             $key = "$TMPL_DIR/$real_symbol:Image";
4202                         }
4203                         if (defined($key)) {
4204                             $SourceSymbolDocs{$key}=$params[$k+1];
4205                             $SourceSymbolSourceFile{$key} = $file;
4206                             $SourceSymbolSourceLine{$key} = $.;
4207                         }
4208                     }
4209                     $SourceSymbolDocs{"$TMPL_DIR/$real_symbol:Long_Description"}=$description;
4210                     $SourceSymbolSourceFile{"$TMPL_DIR/$real_symbol:Long_Description"} = $file;
4211                     $SourceSymbolSourceLine{"$TMPL_DIR/$real_symbol:Long_Description"} = $.;
4212                     #$SourceSymbolTypes{$symbol} = "SECTION";
4213                 } elsif ($symbol =~ m/PROGRAM:\s*(.*)/) {
4214                     my $real_symbol = $1;
4215                     my $key;
4216                     my $section_id;
4218                     @TRACE@("PROGRAM DOCS found in source for '$real_symbol'\n");
4219                     for ($k = 0; $k <= $#params; $k += $PARAM_FIELD_COUNT) {
4220                         @TRACE@("   '".$params[$k]."'\n");
4221                         $params[$k] = "\L$params[$k]";
4222                         undef $key;
4224                         if ($params[$k] eq "short_description") {
4225                             $key = "$TMPL_DIR/$real_symbol:Short_Description";
4226                         } elsif ($params[$k] eq "see_also") {
4227                             $key = "$TMPL_DIR/$real_symbol:See_Also";
4228                         } elsif ($params[$k] eq "section_id") {
4229                             $key = "$TMPL_DIR/$real_symbol:Section_Id";
4230                         } elsif ($params[$k] eq "synopsis") {
4231                             $key = "$TMPL_DIR/$real_symbol:Synopsis";
4232                         } elsif ($params[$k] eq "returns") {
4233                             $key = "$TMPL_DIR/$real_symbol:Returns";
4234                         } elsif ($params[$k] =~ m/^(-.*)/) {
4235                             $key = "$TMPL_DIR/$real_symbol:Options";
4236                             my $opts;
4237                             if (defined($SourceSymbolDocs{$key})) {
4238                                 $opts = $SourceSymbolDocs{$key};
4239                             } else {
4240                                 $opts = [];
4241                             }
4242                             push (@{ $opts }, $1);
4243                             push (@{ $opts }, $params[$k+1]);
4245                             $SourceSymbolDocs{$key} = $opts;
4246                             next;
4247                         }
4248                         if (defined($key)) {
4249                             $SourceSymbolDocs{$key}=$params[$k+1];
4250                             $SourceSymbolSourceFile{$key} = $file;
4251                             $SourceSymbolSourceLine{$key} = $.;
4252                         }
4253                     }
4254                     $SourceSymbolDocs{"$TMPL_DIR/$real_symbol:Long_Description"}=$description;
4255                     $SourceSymbolSourceFile{"$TMPL_DIR/$real_symbol:Long_Description"} = $file;
4256                     $SourceSymbolSourceLine{"$TMPL_DIR/$real_symbol:Long_Description"} = $.;
4258                     $section_id = $SourceSymbolDocs{"$TMPL_DIR/$real_symbol:Section_Id"};
4259                     if (defined ($section_id) && $section_id !~ m/^\s*$/) {
4260                         # Remove trailing blanks and use as is
4261                         $section_id =~ s/\s+$//;
4262                     } else {
4263                         $section_id = &CreateValidSGMLID ("$MODULE-$real_symbol");
4264                     }
4266                     &OutputProgramDBFile ($real_symbol, $section_id);
4268                 } else {
4269                     @TRACE@("SYMBOL DOCS found in source for : '$symbol' ",length($description), "\n");
4270                     $SourceSymbolDocs{$symbol} = $description;
4271                     $SourceSymbolParams{$symbol} = [ @params ];
4272                     # FIXME $SourceSymbolTypes{$symbol} = "STRUCT,SIGNAL,ARG,FUNCTION,MACRO";
4273                     #if (defined $DeclarationTypes{$symbol}) {
4274                     #    $SourceSymbolTypes{$symbol} = $DeclarationTypes{$symbol}
4275                     #}
4276                     $SourceSymbolSourceFile{$symbol} = $file;
4277                     $SourceSymbolSourceLine{$symbol} = $.;
4278                 }
4280                 if ($since_desc) {
4281                      ($since_desc, my @extra_lines) = split ("\n", $since_desc);
4282                      $since_desc =~ s/^\s+//;
4283                      $since_desc =~ s/\s+$//;
4284                      @TRACE@("Since($symbol) : [$since_desc]\n");
4285                      $Since{$symbol} = &ConvertSGMLChars ($symbol, $since_desc);
4286                      if(scalar @extra_lines) {
4287                          &LogWarning ($file, $., "multi-line since docs found");
4288                      }
4289                 }
4291                 if ($stability_desc) {
4292                     $stability_desc = &ParseStabilityLevel($stability_desc, $file, $., "Stability level for $symbol");
4293                     $StabilityLevel{$symbol} = &ConvertSGMLChars ($symbol, $stability_desc);
4294                 }
4296                 if ($deprecated_desc) {
4297                     if (!exists $Deprecated{$symbol}) {
4298                          # don't warn for signals and properties
4299                          #if ($symbol !~ m/::?(.*)/) {
4300                          if (defined $DeclarationTypes{$symbol}) {
4301                              &LogWarning ($file, $.,
4302                                  "$symbol is deprecated in the inline comments, but no deprecation guards were found around the declaration.".
4303                                  " (See the --deprecated-guards option for gtkdoc-scan.)");
4304                          }
4305                     }
4306                     $Deprecated{$symbol} = &ConvertSGMLChars ($symbol, $deprecated_desc);
4307                 }
4308             }
4310             $in_comment_block = 0;
4311             next;
4312         }
4314         # Get rid of ' * ' at start of every line in the comment block.
4315         s%^\s*\*\s?%%;
4316         # But make sure we don't get rid of the newline at the end.
4317         if (!$_) {
4318             $_ = "\n";
4319         }
4320         @TRACE@("scanning :$_");
4322         # If we haven't found the symbol name yet, look for it.
4323         if (!$symbol) {
4324             if (m%^\s*(SECTION:\s*\S+)%) {
4325                 $symbol = $1;
4326                 @TRACE@("SECTION DOCS found in source for : '$symbol'\n");
4327             } elsif (m%^\s*(PROGRAM:\s*\S+)%) {
4328                 $symbol = $1;
4329                 @TRACE@("PROGRAM DOCS found in source for : '$symbol'\n");
4330             } elsif (m%^\s*([\w:-]*\w)\s*:?\s*(\([-A-Za-z0-9._() ]+?\)\s*)*$%) {
4331                 $symbol = $1;
4332                 my $annotation = $2;
4333                 @TRACE@("SYMBOL DOCS found in source for : '$symbol'\n");
4334                 if (defined($annotation)) {
4335                     chomp($annotation);
4336                     if ($annotation ne "") {
4337                         $SymbolAnnotations{$symbol} = $annotation;
4338                         @TRACE@("remaining text for $symbol: '$annotation'\n");
4339                     }
4340                 }
4341             }
4342             next;
4343         }
4345         if ($in_part eq "description") {
4346             # Get rid of 'Description:'
4347             s%^\s*Description:%%;
4348         }
4350         if (m%^\s*(returns|return\s+value):%i) {
4351             # we're in param section and have not seen the blank line
4352             if($in_part ne "") {
4353               $return_desc = $';
4354               $in_part = "return";
4355               next;
4356             }
4357         } elsif (m%^\s*since:%i) {
4358             # we're in param section and have not seen the blank line
4359             if($in_part ne "param") {
4360               $since_desc = $';
4361               $in_part = "since";
4362               next;
4363             }
4364         } elsif (m%^\s*deprecated:%i) {
4365             # we're in param section and have not seen the blank line
4366             if($in_part ne "param") {
4367               $deprecated_desc = $';
4368               $in_part = "deprecated";
4369               next;
4370             }
4371         } elsif (m%^\s*stability:%i) {
4372             $stability_desc = $';
4373             $in_part = "stability";
4374             next;
4375         }
4377         if ($in_part eq "description") {
4378             $description .= $_;
4379             next;
4380         } elsif ($in_part eq "return") {
4381             $return_desc .= $_;
4382             next;
4383         } elsif ($in_part eq "since") {
4384             $since_desc .= $_;
4385             next;
4386         } elsif ($in_part eq "stability") {
4387             $stability_desc .= $_;
4388             next;
4389         } elsif ($in_part eq "deprecated") {
4390             $deprecated_desc .= $_;
4391             next;
4392         }
4394         # We must be in the parameters. Check for the empty line below them.
4395         if (m%^\s*$%) {
4396             $in_part = "description";
4397             next;
4398         }
4400         # Look for a parameter name.
4401         if (m%^\s*@(.+?)\s*:\s*%) {
4402             my $param_name = $1;
4403             my $param_desc = $';
4405             @TRACE@("Found parameter: $param_name\n");
4406             # Allow varargs variations
4407             if ($param_name =~ m/^\.\.\.$/) {
4408                 $param_name = "...";
4409             }
4410             @TRACE@("Found param for symbol $symbol : '$param_name'= '$_'");
4412             push (@params, $param_name);
4413             push (@params, $param_desc);
4414             $current_param += $PARAM_FIELD_COUNT;
4415             $in_part = "param";
4416             next;
4417         } elsif ($in_part eq "") {
4418             @TRACE@("continuation for $symbol annotation '$_'");
4419             my $annotation = $_;
4420             $annotation =~ s/^\s+|\s+$//g ;
4421             $SymbolAnnotations{$symbol} .= $annotation;
4422             next;
4423         }
4425         # We must be in the middle of a parameter description, so add it on
4426         # to the last element in @params.
4427         if ($current_param == -1) {
4428             &LogWarning ($file, $., "Parsing comment block file : parameter expected, but got '$_'");
4429         } else {
4430             $params[$#params] .= $_;
4431         }
4432     }
4433     close (SRCFILE);
4436 #############################################################################
4437 # Function    : OutputMissingDocumentation
4438 # Description : Outputs report of documentation coverage to a file
4440 # Arguments   : none
4441 #############################################################################
4443 sub OutputMissingDocumentation {
4444     my $old_undocumented_file = "$ROOT_DIR/$MODULE-undocumented.txt";
4445     my $new_undocumented_file = "$ROOT_DIR/$MODULE-undocumented.new";
4447     my $n_documented = 0;
4448     my $n_incomplete = 0;
4449     my $total = 0;
4450     my $symbol;
4451     my $percent;
4452     my $msg;
4453     my $buffer = "";
4454     my $buffer_deprecated = "";
4455     my $buffer_descriptions = "";
4457     open(UNDOCUMENTED, ">$new_undocumented_file")
4458       || die "Can't create $new_undocumented_file";
4460     foreach $symbol (sort (keys (%AllSymbols))) {
4461         # FIXME: should we print LogWarnings for undocumented stuff?
4462         # DEBUG
4463         #my $ssfile = &GetSymbolSourceFile($symbol);
4464         #my $ssline = &GetSymbolSourceLine($symbol);
4465         #my $location = "defined at " . (defined($ssfile)?$ssfile:"?") . ":" . (defined($ssline)?$ssline:"0") . "\n";
4466         # DEBUG
4467         if ($symbol !~ /:(Title|Long_Description|Short_Description|See_Also|Stability_Level|Include|Section_Id|Image)/) {
4468             $total++;
4469             if (exists ($AllDocumentedSymbols{$symbol})) {
4470                 $n_documented++;
4471                 if (exists ($AllIncompleteSymbols{$symbol})) {
4472                     $n_incomplete++;
4473                     $buffer .= $symbol . " (" . $AllIncompleteSymbols{$symbol} . ")\n";
4474                     #$buffer .= "\t0: ".$location;
4475                 }
4476             } elsif (exists $Deprecated{$symbol}) {
4477                 if (exists ($AllIncompleteSymbols{$symbol})) {
4478                     $n_incomplete++;
4479                     $buffer_deprecated .= $symbol . " (" . $AllIncompleteSymbols{$symbol} . ")\n";
4480                     #$buffer .= "\t1a: ".$location;
4481                 } else {
4482                     $buffer_deprecated .= $symbol . "\n";
4483                     #$buffer .= "\t1b: ".$location;
4484                 }
4485             } else {
4486                 if (exists ($AllIncompleteSymbols{$symbol})) {
4487                     $n_incomplete++;
4488                     $buffer .= $symbol . " (" . $AllIncompleteSymbols{$symbol} . ")\n";
4489                     #$buffer .= "\t2a: ".$location;
4490                 } else {
4491                     $buffer .= $symbol . "\n";
4492                     #$buffer .= "\t2b: ".$location;
4493                 }
4494             }
4495         } elsif ($symbol =~ /:(Long_Description|Short_Description)/) {
4496             $total++;
4497             if (((exists ($SymbolDocs{$symbol})) && (length ($SymbolDocs{$symbol}) > 0))
4498             || ((exists ($AllDocumentedSymbols{$symbol})) && (length ($AllDocumentedSymbols{$symbol}) > 0))) {
4499               $n_documented++;
4500             } else {
4501               # cut off the leading namespace ($TMPL_DIR)
4502               $symbol =~ m/^.*\/(.*)$/;
4503               $buffer_descriptions .= $1 . "\n";
4504             }
4505         }
4506     }
4508     if ($total == 0) {
4509       $percent = 100;
4510     } else {
4511       $percent = ($n_documented / $total) * 100.0;
4512     }
4514     printf UNDOCUMENTED "%.0f%% symbol docs coverage.\n", $percent;
4515     print UNDOCUMENTED "$n_documented symbols documented.\n";
4516     print UNDOCUMENTED "$n_incomplete symbols incomplete.\n";
4517     print UNDOCUMENTED ($total - $n_documented) . " not documented.\n";
4519     if ($buffer_deprecated ne "") {
4520       $buffer .= "\n" . $buffer_deprecated;
4521     }
4522     if ($buffer_descriptions ne "") {
4523       $buffer .= "\n" . $buffer_descriptions;
4524     }
4525     if ($buffer ne "") {
4526       print UNDOCUMENTED "\n\n$buffer";
4527     }
4528     close (UNDOCUMENTED);
4530     return &UpdateFileIfChanged ($old_undocumented_file, $new_undocumented_file, 0);
4532     printf "%.0f%% symbol docs coverage", $percent;
4533     print "($n_documented symbols documented, $n_incomplete symbols incomplete, " . ($total - $n_documented) . " not documented)\n";
4534     print "See $MODULE-undocumented.txt for a list of missing docs.\nThe doc coverage percentage doesn't include intro sections.\n";
4538 #############################################################################
4539 # Function    : OutputUndeclaredSymbols
4540 # Description : Outputs symbols that are listed in the section file, but not
4541 #               declaration is found in the sources
4543 # Arguments   : none
4544 #############################################################################
4546 sub OutputUndeclaredSymbols {
4547     my $old_undeclared_file = "$ROOT_DIR/$MODULE-undeclared.txt";
4548     my $new_undeclared_file = "$ROOT_DIR/$MODULE-undeclared.new";
4550     open(UNDECLARED, ">$new_undeclared_file")
4551         || die "Can't create $new_undeclared_file";
4553     if (%UndeclaredSymbols) {
4554         print UNDECLARED (join("\n", sort keys %UndeclaredSymbols));
4555         print UNDECLARED "\n";
4556         print "See $MODULE-undeclared.txt for the list of undeclared symbols.\n"
4557     }
4558     close(UNDECLARED);
4560     return &UpdateFileIfChanged ($old_undeclared_file, $new_undeclared_file, 0);
4563 #############################################################################
4564 # Function    : OutputUnusedSymbols
4565 # Description : Outputs symbols that are documented in comments, but not
4566 #               declared in the sources
4568 # Arguments   : none
4569 #############################################################################
4571 sub OutputUnusedSymbols {
4572     my $num_unused = 0;
4573     my $old_unused_file = "$ROOT_DIR/$MODULE-unused.txt";
4574     my $new_unused_file = "$ROOT_DIR/$MODULE-unused.new";
4576     open (UNUSED, ">$new_unused_file")
4577         || die "Can't open $new_unused_file";
4578     my ($symbol);
4579     foreach $symbol (sort keys (%Declarations)) {
4580         if (!defined ($DeclarationOutput{$symbol})) {
4581             print (UNUSED "$symbol\n");
4582             $num_unused++;
4583         }
4584     }
4585     foreach $symbol (sort (keys (%AllUnusedSymbols))) {
4586         print (UNUSED "$symbol(" . $AllUnusedSymbols{$symbol} . ")\n");
4587         $num_unused++;
4588     }
4589     close (UNUSED);
4590     if ($num_unused != 0) {
4591         &LogWarning ($old_unused_file, 1, "$num_unused unused declarations.".
4592             "They should be added to $MODULE-sections.txt in the appropriate place.");
4593     }
4595     return &UpdateFileIfChanged ($old_unused_file, $new_unused_file, 0);
4599 #############################################################################
4600 # Function    : OutputAllSymbols
4601 # Description : Outputs list of all symbols to a file
4603 # Arguments   : none
4604 #############################################################################
4606 sub OutputAllSymbols {
4607      my $n_documented = 0;
4608      my $total = 0;
4609      my $symbol;
4610      my $percent;
4611      my $msg;
4613      open (SYMBOLS, ">$ROOT_DIR/$MODULE-symbols.txt")
4614           || die "Can't create $ROOT_DIR/$MODULE-symbols.txt: $!";
4616      foreach $symbol (sort (keys (%AllSymbols))) {
4617           print SYMBOLS $symbol . "\n";
4618      }
4620      close (SYMBOLS);
4623 #############################################################################
4624 # Function    : OutputSymbolsWithoutSince
4625 # Description : Outputs list of all symbols without a since tag to a file
4627 # Arguments   : none
4628 #############################################################################
4630 sub OutputSymbolsWithoutSince {
4631      my $n_documented = 0;
4632      my $total = 0;
4633      my $symbol;
4634      my $percent;
4635      my $msg;
4637      open (SYMBOLS, ">$ROOT_DIR/$MODULE-nosince.txt")
4638           || die "Can't create $ROOT_DIR/$MODULE-nosince.txt: $!";
4640      foreach $symbol (sort (keys (%SourceSymbolDocs))) {
4641          if (!defined $Since{$symbol}) {
4642              print SYMBOLS $symbol . "\n";
4643          }
4644      }
4646      close (SYMBOLS);
4650 #############################################################################
4651 # Function    : MergeSourceDocumentation
4652 # Description : This merges documentation read from a source file into the
4653 #                documentation read in from a template file.
4655 #                Parameter descriptions override any in the template files.
4656 #                Function descriptions are placed before any description from
4657 #                the template files.
4659 # Arguments   : none
4660 #############################################################################
4662 sub MergeSourceDocumentation {
4663     my $symbol;
4664     my @Symbols;
4666     if (scalar %SymbolDocs) {
4667         @Symbols=keys (%SymbolDocs);
4668         @TRACE@("num existing entries: ".(scalar @Symbols)."\n");
4669     }
4670     else {
4671         # filter scanned declarations, with what we suppress from -sections.txt
4672         my %tmp = ();
4673         foreach $symbol (keys (%Declarations)) {
4674             if (defined($KnownSymbols{$symbol}) && $KnownSymbols{$symbol} == 1) {
4675                 $tmp{$symbol}=1;
4676             }
4677         }
4678         # , add the rest from -sections.txt
4679         foreach $symbol (keys (%KnownSymbols)) {
4680             if ($KnownSymbols{$symbol} == 1) {
4681                 $tmp{$symbol}=1;
4682             }
4683         }
4684         # and add whats found in the source
4685         foreach $symbol (keys (%SourceSymbolDocs)) {
4686             $tmp{$symbol}=1;
4687         }
4688         @Symbols = keys (%tmp);
4689         @TRACE@("num source entries: ".(scalar @Symbols)."\n");
4690     }
4691     foreach $symbol (@Symbols) {
4692         $AllSymbols{$symbol} = 1;
4694         my $have_tmpl_docs = 0;
4696         ## see if the symbol is documented in template
4697         my $tmpl_doc = defined ($SymbolDocs{$symbol}) ? $SymbolDocs{$symbol} : "";
4698         my $check_tmpl_doc =$tmpl_doc;
4699         # remove all xml-tags and whitespaces
4700         $check_tmpl_doc =~ s/<.*?>//g;
4701         $check_tmpl_doc =~ s/\s//g;
4702         # anything left ?
4703         if ($check_tmpl_doc ne "") {
4704             $have_tmpl_docs = 1;
4705         } else {
4706             # if the docs have just an empty para, don't merge that.
4707             $check_tmpl_doc = $tmpl_doc;
4708             $check_tmpl_doc =~ s/(\s|\n)//msg;
4709             if ($check_tmpl_doc eq "<para></para>") {
4710                $tmpl_doc = "";
4711             }
4712         }
4714         if (exists ($SourceSymbolDocs{$symbol})) {
4715             my $type = $DeclarationTypes {$symbol};
4717             @TRACE@("merging [$symbol] from source\n");
4719             my $item = "Parameter";
4720             if (defined ($type)) {
4721                 if ($type eq 'STRUCT') {
4722                     $item = "Field";
4723                 } elsif ($type eq 'ENUM') {
4724                     $item = "Value";
4725                 } elsif ($type eq 'UNION') {
4726                     $item = "Field";
4727                 }
4728             } else {
4729                 $type="SIGNAL";
4730             }
4732             my $src_doc = $SourceSymbolDocs{$symbol};
4733             # remove leading and training whitespaces
4734             $src_doc =~ s/^\s+//;
4735             $src_doc =~ s/\s+$//;
4737             # Don't output warnings for overridden titles as titles are
4738             # automatically generated in the -sections.txt file, and thus they
4739             # are often overridden.
4740             if ($have_tmpl_docs && $symbol !~ m/:Title$/) {
4741                 # check if content is different
4742                 if ($tmpl_doc ne $src_doc) {
4743                     #print "[$tmpl_doc] [$src_doc]\n";
4744                     &LogWarning ($SourceSymbolSourceFile{$symbol}, $SourceSymbolSourceLine{$symbol},
4745                         "Documentation in template ".$SymbolSourceFile{$symbol}.":".$SymbolSourceLine{$symbol}." for $symbol being overridden by inline comments.");
4746                 }
4747             }
4749             if ($src_doc ne "") {
4750                  $AllDocumentedSymbols{$symbol} = 1;
4751             }
4753             # Do not add <para> to nothing, it breaks missing docs checks.
4754             my $src_doc_para = "";
4755             if ($src_doc ne "") {
4756                 $src_doc_para = $src_doc;
4757             }
4759             if ($symbol =~ m/$TMPL_DIR\/.+:Long_Description/) {
4760                 $SymbolDocs{$symbol} = "$src_doc_para$tmpl_doc";
4761             } elsif ($symbol =~ m/$TMPL_DIR\/.+:.+/) {
4762                 # For the title/summary/see also section docs we don't want to
4763                 # add any <para> tags.
4764                 $SymbolDocs{$symbol} = "$src_doc"
4765             } else {
4766                 $SymbolDocs{$symbol} = "$src_doc_para$tmpl_doc";
4767             }
4769             # merge parameters
4770             if ($symbol =~ m/.*::.*/) {
4771                 # For signals we prefer the param names from the source docs,
4772                 # since the ones from the templates are likely to contain the
4773                 # artificial argn names which are generated by gtkdoc-scangobj.
4774                 $SymbolParams{$symbol} = $SourceSymbolParams{$symbol};
4775                 # FIXME: we need to check for empty docs here as well!
4776             } else {
4777                 # The templates contain the definitive parameter names and order,
4778                 # so we will not change that. We only override the actual text.
4779                 my $tmpl_params = $SymbolParams{$symbol};
4780                 if (!defined ($tmpl_params)) {
4781                     @TRACE@("No merge needed for $symbol\n");
4782                     $SymbolParams{$symbol} = $SourceSymbolParams{$symbol};
4783                     #  FIXME: we still like to get the number of params and merge
4784                     #  1) we would noticed that params have been removed/renamed
4785                     #  2) we would catch undocumented params
4786                     #  params are not (yet) exported in -decl.txt so that we
4787                     #  could easily grab them :/
4788                 } else {
4789                     my $params = $SourceSymbolParams{$symbol};
4790                     my $j;
4791                     @TRACE@("Merge needed for $symbol, tmpl_params: ",$#$tmpl_params,", source_params: ",$#$params," \n");
4792                     for ($j = 0; $j <= $#$tmpl_params; $j += $PARAM_FIELD_COUNT) {
4793                         my $tmpl_param_name = $$tmpl_params[$j];
4795                         # Try to find the param in the source comment documentation.
4796                         my $found = 0;
4797                         my $k;
4798                         @TRACE@("  try merge param $tmpl_param_name\n");
4799                         for ($k = 0; $k <= $#$params; $k += $PARAM_FIELD_COUNT) {
4800                             my $param_name = $$params[$k];
4801                             my $param_desc = $$params[$k + 1];
4803                             @TRACE@("    test param  $param_name\n");
4804                             # We accept changes in case, since the Gnome source
4805                             # docs contain a lot of these.
4806                             if ("\L$param_name" eq "\L$tmpl_param_name") {
4807                                 $found = 1;
4809                                 # Override the description.
4810                                 $$tmpl_params[$j + 1] = $param_desc;
4812                                 # Set the name to "" to mark it as used.
4813                                 $$params[$k] = "";
4814                                 last;
4815                             }
4816                         }
4818                         # If it looks like the parameters are there, but not
4819                         # in the right place, try to explain a bit better.
4820                         if ((!$found) && ($src_doc =~ m/\@$tmpl_param_name:/)) {
4821                             &LogWarning (&GetSymbolSourceFile ($symbol), &GetSymbolSourceLine($symbol),
4822                                 "Parameters for $symbol must start on the line immediately after the function or macro name.");
4823                         }
4824                     }
4826                     # Now we output a warning if parameters have been described which
4827                     # do not exist.
4828                     for ($j = 0; $j <= $#$params; $j += $PARAM_FIELD_COUNT) {
4829                         my $param_name = $$params[$j];
4830                         if ($param_name) {
4831                             # the template builder cannot detect if a macro returns
4832                             # a result or not
4833                             if(($type eq "MACRO") && ($param_name eq "Returns")) {
4834                                 # FIXME: do we need to add it then to tmpl_params[] ?
4835                                 my $num=$#$tmpl_params;
4836                                 @TRACE@("  adding Returns: to macro docs for $symbol.\n");
4837                                 $$tmpl_params[$num+1]="Returns";
4838                                 $$tmpl_params[$num+2]=$$params[$j+1];
4839                                 next;
4840                             }
4841                             &LogWarning (&GetSymbolSourceFile ($symbol), &GetSymbolSourceLine($symbol),
4842                                 "$item described in source code comment block but does not exist. $type: $symbol $item: $param_name.");
4843                         }
4844                     }
4845                 }
4846             }
4847         } else {
4848             if ($have_tmpl_docs) {
4849                 $AllDocumentedSymbols{$symbol} = 1;
4850                 @TRACE@("merging [$symbol] from template\n");
4851             }
4852             else {
4853                 @TRACE@("[$symbol] undocumented\n");
4854             }
4855         }
4857         # if this symbol is documented, check if docs are complete
4858         $check_tmpl_doc = defined ($SymbolDocs{$symbol}) ? $SymbolDocs{$symbol} : "";
4859         # remove all xml-tags and whitespaces
4860         $check_tmpl_doc =~ s/<.*?>//g;
4861         $check_tmpl_doc =~ s/\s//g;
4862         if ($check_tmpl_doc ne "") {
4863             my $tmpl_params = $SymbolParams{$symbol};
4864             if (defined ($tmpl_params)) {
4865                 my $type = $DeclarationTypes {$symbol};
4867                 my $item = "Parameter";
4868                 if (defined ($type)) {
4869                     if ($type eq 'STRUCT') {
4870                         $item = "Field";
4871                     } elsif ($type eq 'ENUM') {
4872                         $item = "Value";
4873                     } elsif ($type eq 'UNION') {
4874                         $item = "Field";
4875                     }
4876                 } else {
4877                     $type="SIGNAL";
4878                 }
4880                 @TRACE@("Check param docs for $symbol, tmpl_params: ",$#$tmpl_params," entries, type=$type\n");
4882                 if ($#$tmpl_params > 0) {
4883                     my $j;
4884                     for ($j = 0; $j <= $#$tmpl_params; $j += $PARAM_FIELD_COUNT) {
4885                         # Output a warning if the parameter is empty and
4886                         # remember for stats.
4887                         my $tmpl_param_name = $$tmpl_params[$j];
4888                         my $tmpl_param_desc = $$tmpl_params[$j + 1];
4889                         if ($tmpl_param_name ne "void" && $tmpl_param_desc !~ m/\S/) {
4890                             if (exists ($AllIncompleteSymbols{$symbol})) {
4891                                 $AllIncompleteSymbols{$symbol}.=", ".$tmpl_param_name;
4892                             } else {
4893                                 $AllIncompleteSymbols{$symbol}=$tmpl_param_name;
4894                             }
4895                             &LogWarning (&GetSymbolSourceFile ($symbol), &GetSymbolSourceLine($symbol),
4896                                 "$item description for $symbol"."::"."$tmpl_param_name is missing in source code comment block.");
4897                         }
4898                     }
4899                 }
4900                 else {
4901                     if ($#$tmpl_params == 0) {
4902                         $AllIncompleteSymbols{$symbol}="<items>";
4903                         &LogWarning (&GetSymbolSourceFile ($symbol), &GetSymbolSourceLine($symbol),
4904                             "$item descriptions for $symbol are missing in source code comment block.");
4905                     }
4906                     # $#$tmpl_params==-1 means we don't know about parameters
4907                     # this unfortunately does not tell if there should be some
4908                 }
4909             }
4910         }
4911    }
4912    @TRACE@("num doc entries: ".(scalar %SymbolDocs)."\n");
4915 #############################################################################
4916 # Function    : IsEmptyDoc
4917 # Description : Check if a doc-string is empty. Its also regarded as empty if
4918 #               it only consist of whitespace or e.g. FIXME.
4919 # Arguments   : the doc-string
4920 #############################################################################
4921 sub IsEmptyDoc {
4922     my ($doc) = @_;
4924     if ($doc =~ /^\s*$/) {
4925         return 1;
4926     }
4928     if ($doc =~ /^\s*<para>\s*(FIXME)?\s*<\/para>\s*$/) {
4929         return 1;
4930     }
4932     return 0;
4935 #############################################################################
4936 # Function    : ConvertMarkDown
4937 # Description : Converts mark down syntax to the respective docbook.
4938 #               http://de.wikipedia.org/wiki/Markdown
4939 #               Inspired by the design of ParseDown
4940 #               http://parsedown.org/
4941 #               Copyright (c) 2013 Emanuil Rusev, erusev.com
4942 # Arguments   : the symbol name, the doc-string
4943 #############################################################################
4945 sub ConvertMarkDown {
4946     my ($symbol, $text) = @_;
4948     $text = &MarkDownParse ($text, $symbol);
4950     return $text
4953 # SUPPORTED MARKDOWN
4954 # ==================
4956 # Atx-style Headers
4957 # -----------------
4959 # # Header 1
4961 # ## Header 2 ##
4963 # Setext-style Headers
4964 # --------------------
4966 # Header 1
4967 # ========
4969 # Header 2
4970 # --------
4972 # Ordered (unnested) Lists
4973 # ------------------------
4975 # 1. item 1
4977 # 1. item 2 with loooong
4978 #    description
4980 # 3. item 3
4982 # Note: we require a blank line above the list items
4985 # TODO(ensonic): it would be nice to add id parameters to the refsect2 elements
4987 sub MarkDownParseBlocks {
4988   my ($linesref, $symbol, $context) = @_;
4989   my $line;
4990   my @md_blocks = ();
4991   my $md_block = { type => "" };
4993  OUTER: foreach $line (@$linesref) {
4994     my $first_char = substr ($line, 0, 1);
4995     my $deindented_line;
4997     @TRACE@("in '".$md_block->{"type"}."' state, parsing '$line'");
4999     if ($md_block->{"type"} eq "markup") {
5000       if (!$md_block->{"closed"}) {
5001         if (index ($line, $md_block->{"start"}) != -1) {
5002           $md_block->{"depth"}++;
5003         }
5004         if (index ($line, $md_block->{"end"}) != -1) {
5005           if ($md_block->{"depth"} > 0) {
5006             $md_block->{"depth"}--;
5007           } else {
5008             @TRACE@("closing tag '$line'");
5009             $md_block->{"closed"} = 1;
5010             # TODO(ensonic): reparse inner text with MarkDownParseLines?
5011           }
5012         }
5013         $md_block->{"text"} .= "\n" . $line;
5014         @TRACE@("add to markup");
5015         next OUTER;
5016       }
5017     }
5019     $deindented_line = $line;
5020     $deindented_line =~ s/^\s+//;
5022     if ($md_block->{"type"} eq "heading") {
5023       # a heading is ended by any level less than or equal
5024       if ($md_block->{"level"} == 1) {
5025         if ($line =~ /^={4,}[ \t]*$/) {
5026           my $text = pop @{$md_block->{"lines"}};
5027           $md_block->{"interrupted"} = 0;
5028           push @md_blocks, $md_block;
5030           $md_block = { type => "heading",
5031                         text => $text,
5032                         lines => [],
5033                         level => 1 };
5034           next OUTER;
5035         } elsif ($line =~ /^[#][ \t]+(.+?)[ \t]*[#]*[ \t]*(?:{#([^}]+)})?[ \t]*$/) {
5036           $md_block->{"interrupted"} = 0;
5037           push @md_blocks, $md_block;
5039           $md_block = { type => "heading",
5040                         text => $1,
5041                         id => $2,
5042                         lines => [],
5043                         level => 1 };
5044           next OUTER;
5045         } else {
5046           # push lines into the block until the end is reached
5047           push @{$md_block->{"lines"}}, $line;
5048           next OUTER;
5049         }
5050       } else {
5051         if ($line =~ /^[=]{4,}[ \t]*$/) {
5052           my $text = pop @{$md_block->{"lines"}};
5053           $md_block->{"interrupted"} = 0;
5054           push @md_blocks, $md_block;
5056           $md_block = { type => "heading",
5057                         text => $text,
5058                         lines => [],
5059                         level => 1 };
5060           next OUTER;
5061         } elsif ($line =~ /^[-]{4,}[ \t]*$/) {
5062           my $text = pop @{$md_block->{"lines"}};
5063           $md_block->{"interrupted"} = 0;
5064           push @md_blocks, $md_block;
5066           $md_block = { type => "heading",
5067                         text => $text,
5068                         lines => [],
5069                         level => 2 };
5070           next OUTER;
5071         } elsif ($line =~ /^([#]{1,2})[ \t]+(.+?)[ \t]*[#]*[ \t]*(?:{#([^}]+)})?[ \t]*$/) {
5072           $md_block->{"interrupted"} = 0;
5073           push @md_blocks, $md_block;
5075           $md_block = { type => "heading",
5076                         text => $2,
5077                         id => $3,
5078                         lines => [],
5079                         level => length($1) };
5080           next OUTER;
5081         } else {
5082           # push lines into the block until the end is reached
5083           push @{$md_block->{"lines"}}, $line;
5084           next OUTER;
5085         }
5086       }
5087     } elsif ($md_block->{"type"} eq "code") {
5088       if ($line =~ /^[ \t]*\]\|(.*)/) {
5089         push @md_blocks, $md_block;
5090         $md_block = { type => "paragraph",
5091                       text => "$1",
5092                       lines => [] };
5093       } else {
5094         push @{$md_block->{"lines"}}, $line;
5095       }
5096       next OUTER;
5097     }
5099     if ($deindented_line eq "") {
5100       $md_block->{"interrupted"} = 1;
5101       next;
5102     }
5104     if ($md_block->{"type"} eq "quote") {
5105       if (!$md_block->{"interrupted"}) {
5106         $line =~ s/^[ ]*>[ ]?//;
5107         push @{$md_block->{"lines"}}, $line;
5108         next OUTER;
5109       }
5110     } elsif ($md_block->{"type"} eq "li") {
5111       my $marker = $md_block->{"marker"};
5112       if ($line =~ /^([ ]{0,3})($marker)[ ](.*)/) {
5113         my $indentation = $1;
5114         if ($md_block->{"indentation"} ne $indentation) {
5115           push @{$md_block->{"lines"}}, $line;
5116         } else {
5117           my $lines = $3;
5118           my $ordered = $md_block->{"ordered"};
5119           $lines =~ s/^[ ]{0,4}//;
5120           $md_block->{"last"} = 0;
5121           push @md_blocks, $md_block;
5122           $md_block = { type => "li",
5123                         ordered => $ordered,
5124                         indentation => $indentation,
5125                         marker => $marker,
5126                         first => 0,
5127                         last => 1,
5128                         lines => [ $lines ] };
5129         }
5130         next OUTER;
5131       }
5133       if ($md_block->{"interrupted"}) {
5134         if ($first_char eq " ") {
5135           push @{$md_block->{"lines"}}, "";
5136           $line =~ s/^[ ]{0,4}//;
5137           push @{$md_block->{"lines"}}, $line;
5138           $md_block->{"interrupted"} = 0;
5139           next OUTER;
5140         }
5141       } else {
5142         $line =~ s/^[ ]{0,4}//;
5143         push @{$md_block->{"lines"}}, $line;
5144         next OUTER;
5145       }
5146     }
5148     # indentation sensitive types
5149     @TRACE@("parsing '$line'");
5151     if ($line =~ /^([#]{1,2})[ \t]+(.+?)[ \t]*[#]*[ \t]*(?:{#([^}]+)})?[ \t]*$/) {
5152       # atx heading (#)
5153       push @md_blocks, $md_block;
5155       $md_block = { type => "heading",
5156                     text => $2,
5157                     id => $3,
5158                     lines => [],
5159                     level => length($1) };
5161       next OUTER;
5162     } elsif ($line =~ /^={4,}[ \t]*$/) {
5163       # setext heading (====)
5165       if ($md_block->{"type"} eq "paragraph" && $md_block->{"interrupted"}) {
5166         push @md_blocks, $md_block;
5167         $md_block->{"type"} = "heading";
5168         $md_block->{"lines"} = [];
5169         $md_block->{"level"} = 1;
5170       }
5172       next OUTER;
5173     } elsif ($line =~ /^-{4,}[ \t]*$/) {
5174       # setext heading (-----)
5176       if ($md_block->{"type"} eq "paragraph" && $md_block->{"interrupted"}) {
5177         push @md_blocks, $md_block;
5178         $md_block->{"type"} = "heading";
5179         $md_block->{"lines"} = [];
5180         $md_block->{"level"} = 2;
5181       }
5183       next OUTER;
5184     } elsif ($line =~ /^[ \t]*\|\[[ ]*(?:<!-- language="([^"]+?)" -->)?/) {
5185       # code
5186       $md_block->{"interrupted"} = 1;
5187       push @md_blocks, $md_block;
5188       $md_block = { type => "code",
5189                     language => $1,
5190                     lines => [] };
5191       next OUTER;
5192     }
5194     # indentation insensitive types
5195     if ($line =~ /^[ ]*<!DOCTYPE/) {
5196       push @md_blocks, $md_block;
5198       $md_block = { type   => "markup",
5199                     text   => $deindented_line,
5200                     start  => "<",
5201                     end    => ">",
5202                     closed => 0,
5203                     depth  => 0 };
5205     } elsif ($line =~ /^[ ]*<\??(\w+)[^>]*([\/\?])?[ \t]*>/) {
5206       # markup, including <?xml version="1.0"?>
5207       my $tag = $1;
5208       my $is_self_closing = defined($2);
5210       # skip link markdown
5211       # TODO(ensonic): consider adding more uri schemes (ftp, ...)
5212       if ($tag =~ /^https?/) {
5213         @TRACE@("skipping link '$tag'");
5214       } else {
5215         # for TEXT_LEVEL_ELEMENTS, we want to keep them as-is in the paragraph
5216         # instead of creation a markdown block.
5217         my $scanning_for_end_of_text_level_tag = (
5218             $md_block->{"type"} eq "paragraph" &&
5219             defined($md_block->{"start"}) &&
5220             !$md_block->{"closed"});
5221         @TRACE@("markup found '$tag', scanning $scanning_for_end_of_text_level_tag ?");
5222         if (!$MD_TEXT_LEVEL_ELEMENTS{$tag} && !$scanning_for_end_of_text_level_tag) {
5223           push @md_blocks, $md_block;
5225           if ($is_self_closing) {
5226             @TRACE@("self-closing docbook '$tag'");
5227             $md_block = { type => "self-closing tag",
5228                           text => $deindented_line };
5229             $is_self_closing = 0;
5230             next OUTER;
5231           }
5233           @TRACE@("new markup '$tag'");
5234           $md_block = { type   => "markup",
5235                         text   => $deindented_line,
5236                         start  => "<" . $tag . ">",
5237                         end    => "</" . $tag . ">",
5238                         closed => 0,
5239                         depth  => 0 };
5240           if ($deindented_line =~ /<\/$tag>/) {
5241             $md_block->{"closed"} = 1;
5242           }
5243           next OUTER;
5244         } else {
5245           if ($MD_TEXT_LEVEL_ELEMENTS{$tag}) {
5246             @TRACE@("text level docbook '$tag' in '".$md_block->{"type"}."' state");
5247             # TODO(ensonic): handle nesting
5248             if (!$scanning_for_end_of_text_level_tag) {
5249               if ($deindented_line !~ /<\/$tag>/) {
5250                 @TRACE@("new text level markup '$tag'");
5251                 $md_block->{"start"} = "<" . $tag . ">";
5252                 $md_block->{"end"} = "</" . $tag . ">";
5253                 $md_block->{"closed"} = 0;
5254                 @TRACE@("scanning for end of '$tag'");
5255               }
5256             } else {
5257               if ($deindented_line =~ /$md_block->{"end"}/) {
5258                 $md_block->{"closed"} = 1;
5259                 @TRACE@("found end of '$tag'");
5260               }
5261             }
5262           }
5263         }
5264       }
5265     } elsif ($line =~ /^([ ]*)[*+-][ ](.*)/) {
5266       # li
5267       push @md_blocks, $md_block;
5268       my $lines = $2;
5269       my $indentation = $1;
5270       $lines =~ s/^[ ]{0,4}//;
5271       $md_block = { type => "li",
5272                     ordered => 0,
5273                     indentation => $indentation,
5274                     marker => "[*+-]",
5275                     first => 1,
5276                     last => 1,
5277                     lines => [ $lines ] };
5278       next OUTER;
5279     } elsif ($line =~ /^[ ]*>[ ]?(.*)/) {
5280       push @md_blocks, $md_block;
5281       $md_block = { type => "quote",
5282                     lines => [ $1 ] };
5283       next OUTER;
5284     }
5286     # list item
5287     if ($line =~ /^([ ]{0,4})\d+[.][ ]+(.*)/) {
5288       push @md_blocks, $md_block;
5289       my $lines = $2;
5290       my $indentation = $1;
5291       $lines =~ s/^[ ]{0,4}//;
5293       $md_block = { type => "li",
5294                     ordered => 1,
5295                     indentation => $indentation,
5296                     marker => "\\d+[.]",
5297                     first => 1,
5298                     last => 1,
5299                     lines => [ $lines ] };
5301       next;
5302     }
5304     # paragraph
5305     if ($md_block->{"type"} eq "paragraph") {
5306       if ($md_block->{"interrupted"}) {
5307         push @md_blocks, $md_block;
5308         $md_block = { type => "paragraph",
5309                       interrupted => 0,
5310                       text => $line };
5311         @TRACE@("new paragraph due to interrupted");
5312       } else {
5313         $md_block->{"text"} .= "\n" . $line;
5314         @TRACE@("add to paragraph");
5315       }
5316     } else {
5317       push @md_blocks, $md_block;
5318       $md_block = { type => "paragraph",
5319                     text => $line };
5320       @TRACE@("new paragraph due to different block type");
5321     }
5322   }
5324   push @md_blocks, $md_block;
5326   shift @md_blocks;
5328   return @md_blocks;
5331 sub MarkDownParseSpanElementsInner {
5332   my ($text, $markersref) = @_;
5333   my $markup = "";
5334   my %markers = map { $_ => 1 } @$markersref;
5336   while ($text ne "") {
5337     my $closest_marker = "";
5338     my $closest_marker_index = 0;
5339     my $closest_marker_position = -1;
5340     my $text_marker = "";
5341     my $i = 0;
5342     my $offset = 0;
5343     my @markers_rest;
5344     my $marker;
5345     my $use;
5347     while ( ($marker, $use) = each %markers ) {
5348       my $marker_position;
5350       if (!$use) {
5351         next;
5352       }
5354       $marker_position = index ($text, $marker);
5356       if ($marker_position < 0) {
5357         $markers{$marker} = 0;
5358         next;
5359       }
5361       if ($closest_marker eq "" || $marker_position < $closest_marker_position) {
5362         $closest_marker = $marker;
5363         $closest_marker_index = $i;
5364         $closest_marker_position = $marker_position;
5365       }
5366     }
5368     if ($closest_marker_position >= 0) {
5369       $text_marker = substr ($text, $closest_marker_position);
5370     }
5372     if ($text_marker eq "") {
5373       $markup .= $text;
5374       $text = "";
5375       next; # last
5376     }
5378     $markup .= substr ($text, 0, $closest_marker_position);
5379     $text = substr ($text, $closest_marker_position);
5380     @markers_rest = map { $markers{$_} ? ($_ eq $closest_marker ? () : $_) : () } keys %markers;
5382     if ($closest_marker eq "![" || $closest_marker eq "[") {
5383       my %element;
5385       if (index ($text, "]") && $text =~ /\[((?:[^][]|(?R))*)\]/) {
5386         my $remaining_text;
5388         %element = ( "!" => (substr ($text, 0, 1) eq "!"),
5389                      "a" => $1 );
5391         $offset = length ($&);
5392         if ($element{"!"}) {
5393           $offset++;
5394         }
5396         $remaining_text = substr ($text, $offset);
5397         if ($remaining_text =~ /^\([ ]*([^)'"]*?)(?:[ ]+['"](.+?)['"])?[ ]*\)/) {
5398           $element{"»"} = $1;
5399           if (defined ($2)) {
5400             $element{"#"} = $2;
5401           }
5402           $offset += length ($&);
5403         } elsif ($remaining_text =~ /^\s*\[([^\]<]*?)\]/) {
5404           $element{"ref"} = $1;
5405           $offset += length ($&);
5406         } else {
5407           undef %element;
5408         }
5409       }
5411       if (%element) {
5412         if ($element{"»"}) {
5413           $element{"»"} =~ s/&/&amp;/g;
5414           $element{"»"} =~ s/</&lt;/g;
5415         }
5416         if ($element{"!"}) {
5417           $markup .= "<inlinemediaobject><imageobject><imagedata fileref=\"" . $element{"»"} . "\"></imagedata></imageobject>";
5419           if (defined ($element{"a"})) {
5420             $markup .= "<textobject><phrase>" . $element{"a"} . "</phrase></textobject>";
5421           }
5423           $markup .= "</inlinemediaobject>";
5424         } elsif ($element{"ref"}) {
5425           $element{"a"} = &MarkDownParseSpanElementsInner ($element{"a"}, \@markers_rest);
5426           $markup .= "<link linkend=\"" . $element{"ref"} . "\"";
5428           if (defined ($element{"#"})) {
5429             # title attribute not supported
5430           }
5432           $markup .= ">" . $element{"a"} . "</link>";
5433         } else {
5434           $element{"a"} = &MarkDownParseSpanElementsInner ($element{"a"}, \@markers_rest);
5435           $markup .= "<ulink url=\"" . $element{"»"} . "\"";
5437           if (defined ($element{"#"})) {
5438             # title attribute not supported
5439           }
5441           $markup .= ">" . $element{"a"} . "</ulink>";
5442         }
5443       } else {
5444         $markup .= $closest_marker;
5445         if ($closest_marker eq "![") {
5446           $offset = 2;
5447         } else {
5448           $offset = 1;
5449         }
5450       }
5451     } elsif ($closest_marker eq "<") {
5452       if ($text =~ /^<(https?:[\/]{2}[^\s]+?)>/i) {
5453         my $element_url = $1;
5454         $element_url =~ s/&/&amp;/g;
5455         $element_url =~ s/</&lt;/g;
5457         $markup .= "<ulink url=\"" . $element_url . "\">" . $element_url . "</ulink>";
5458         $offset = length ($&);
5459       } elsif ($text =~ /^<([A-Za-z0-9._-]+?@[A-Za-z0-9._-]+?)>/) {
5460         $markup .= "<ulink url=\"mailto:" . $1 . "\">" . $1 . "</ulink>";
5461         $offset = length ($&);
5462       } elsif ($text =~ /^<[^>]+?>/) {
5463         $markup .= $&;
5464         $offset = length ($&);
5465       } else {
5466         $markup .= "&lt;";
5467         $offset = 1;
5468       }
5469     } elsif ($closest_marker eq "\\") {
5470       my $special_char = substr ($text, 1, 1);
5471       if ($MD_ESCAPABLE_CHARS{$special_char} ||
5472           $MD_GTK_ESCAPABLE_CHARS{$special_char}) {
5473         $markup .= $special_char;
5474         $offset = 2;
5475       } else {
5476         $markup .= "\\";
5477         $offset = 1;
5478       }
5479     } elsif ($closest_marker eq "`") {
5480       if ($text =~ /^(`+)([^`]+?)\1(?!`)/) {
5481         my $element_text = $2;
5482         $markup .= "<literal>" . $element_text . "</literal>";
5483         $offset = length ($&);
5484       } else {
5485         $markup .= "`";
5486         $offset = 1;
5487       }
5488     } elsif ($closest_marker eq "@") {
5489       # Convert '@param()'
5490       # FIXME: we could make those also links ($symbol.$2), but that would be less
5491       # useful as the link target is a few lines up or down
5492       if ($text =~ /^(\A|[^\\])\@(\w+((\.|->)\w+)*)\s*\(\)/) {
5493         $markup .= $1 . "<parameter>" . $2 . "()</parameter>\n";
5494         $offset = length ($&);
5495       } elsif ($text =~ /^(\A|[^\\])\@(\w+((\.|->)\w+)*)/) {
5496         # Convert '@param', but not '\@param'.
5497         $markup .= $1 . "<parameter>" . $2 . "</parameter>\n";
5498         $offset = length ($&);
5499       } elsif ($text =~ /^\\\@/) {
5500         $markup .= "\@";
5501         $offset = length ($&);
5502       } else {
5503         $markup .= "@";
5504         $offset = 1;
5505       }
5506     } elsif ($closest_marker eq "#") {
5507       if ($text =~ /^(\A|[^\\])#([\w\-:\.]+[\w]+)\s*\(\)/) {
5508         # handle #Object.func()
5509         $markup .= $1 . &MakeXRef ($2, &tagify ($2 . "()", "function"));
5510         $offset = length ($&);
5511       } elsif ($text =~ /^(\A|[^\\])#([\w\-:\.]+[\w]+)/) {
5512         # Convert '#symbol', but not '\#symbol'.
5513         $markup .= $1 . &MakeHashXRef ($2, "type");
5514         $offset = length ($&);
5515       } elsif ($text =~ /^\\#/) {
5516         $markup .= "#";
5517         $offset = length ($&);
5518       } else {
5519         $markup .= "#";
5520         $offset = 1;
5521       }
5522     } elsif ($closest_marker eq "%") {
5523       if ($text =~ /^(\A|[^\\])\%(-?\w+)/) {
5524         # Convert '%constant', but not '\%constant'.
5525         # Also allow negative numbers, e.g. %-1.
5526         $markup .= $1 . &MakeXRef ($2, &tagify ($2, "literal"));
5527         $offset = length ($&);
5528       } elsif ($text =~ /^\\%/) {
5529         $markup .= "\%";
5530         $offset = length ($&);
5531       } else {
5532         $markup .= "%";
5533         $offset = 1;
5534       }
5535     }
5537     if ($offset > 0) {
5538       $text = substr ($text, $offset);
5539     }
5540   }
5542   return $markup;
5545 sub MarkDownParseSpanElements {
5546   my ($text) = @_;
5547   my @markers = ( "\\", "<", "![", "[", "`", "%", "#", "@" );
5549   $text = &MarkDownParseSpanElementsInner ($text, \@markers);
5551   # Convert 'function()' or 'macro()'.
5552   # if there is abc_*_def() we don't want to make a link to _def()
5553   # FIXME: also handle abc(def(....)) : but that would need to be done recursively :/
5554   $text =~ s/([^\*.\w])(\w+)\s*\(\)/$1.&MakeXRef($2, &tagify($2 . "()", "function"));/eg;
5556   return $text;
5559 sub ReplaceEntities {
5560   my ($text, $symbol) = @_;
5561   my $warn = "";
5562   my @entities = ( [ "&lt;", "<" ],
5563                    [ "&gt;", ">" ],
5564                    [ "&ast;", "*" ],
5565                    [ "&num;", "#" ],
5566                    [ "&percnt;", "%"],
5567                    [ "&colon;", ":" ],
5568                    [ "&quot;", "\"" ],
5569                    [ "&apos;", "'" ],
5570                    [ "&nbsp;", " " ],
5571                    [ "&amp;", "&" ] ); # Do this last, or the others get messed up.
5572   my $i;
5574   # Expand entities in <programlisting> even inside CDATA since
5575   # we changed the definition of |[ to add CDATA
5576   for ($i = 0; $i <= $#entities; $i++) {
5577     $text =~ s/$entities[$i][0]/$entities[$i][1]/g;
5578   }
5580   return $text;
5583 sub MarkDownOutputDocBook {
5584   my ($blocksref, $symbol, $context) = @_;
5585   my $output = "";
5586   my $block;
5587   my @blocks = @$blocksref;
5589   foreach $block (@blocks) {
5590     my $text;
5591     my $title;
5593     #$output .= "\n<!-- beg type='" . $block->{"type"} . "'-->\n";
5595     if ($block->{"type"} eq "paragraph") {
5596       $text = &MarkDownParseSpanElements ($block->{"text"});
5597       if ($context eq "li" && $output eq "") {
5598         if ($block->{"interrupted"}) {
5599           $output .= "\n<para>$text</para>\n";
5600         } else {
5601           $output .= "<para>$text</para>";
5602           if ($#blocks > 0) {
5603             $output .= "\n";
5604           }
5605         }
5606       } else {
5607         $output .= "<para>$text</para>\n";
5608       }
5610     } elsif ($block->{"type"} eq "heading") {
5611       my $tag;
5613       $title = &MarkDownParseSpanElements ($block->{"text"});
5615       if ($block->{"level"} == 1) {
5616         $tag = "refsect2";
5617       } else {
5618         $tag = "refsect3";
5619       }
5621       $text = &MarkDownParseLines ($block->{"lines"}, $symbol, "heading");
5622       if (defined ($block->{"id"})) {
5623         $output .= "<$tag id=\"" . $block->{"id"} . "\">";
5624       } else {
5625         $output .= "<$tag>";
5626       }
5628       $output .= "<title>$title</title>$text</$tag>\n";
5629     } elsif ($block->{"type"} eq "li") {
5630       my $tag = "itemizedlist";
5632       if ($block->{"first"}) {
5633         if ($block->{"ordered"}) {
5634           $tag = "orderedlist";
5635         }
5636         $output .= "<$tag>\n";
5637       }
5639       if ($block->{"interrupted"}) {
5640         push @{$block->{"lines"}}, "";
5641       }
5643       $text = &MarkDownParseLines ($block->{"lines"}, $symbol, "li");
5644       $output .= "<listitem>".$text."</listitem>\n";
5645       if ($block->{"last"}) {
5646         if ($block->{"ordered"}) {
5647           $tag = "orderedlist";
5648         }
5649         $output .= "</$tag>\n";
5650       }
5651     } elsif ($block->{"type"} eq "quote") {
5652       $text = &MarkDownParseLines ($block->{"lines"}, $symbol, "quote");
5653       $output .= "<blockquote>\n$text</blockquote>\n";
5654     } elsif ($block->{"type"} eq "code") {
5655       my $tag = "programlisting";
5657       if ($block->{"language"}) {
5658         if ($block->{"language"} eq "plain") {
5659           $output .= "<informalexample><screen><![CDATA[\n";
5660           $tag = "screen";
5661         } else {
5662           $output .= "<informalexample><programlisting language=\"" . $block->{"language"} . "\"><![CDATA[\n";
5663         }
5664       } else {
5665         $output .= "<informalexample><programlisting><![CDATA[\n";
5666       }
5667       foreach (@{$block->{"lines"}}) {
5668         $output .= &ReplaceEntities ($_, $symbol) . "\n";
5669       }
5670       $output .= "]]></$tag></informalexample>\n";
5671     } elsif ($block->{"type"} eq "markup") {
5672       $text = &ExpandAbbreviations($symbol, $block->{"text"});
5673       $output .= $text."\n";
5674     } else {
5675       $output .= $block->{"text"}."\n";
5676     }
5677     #$output .= "\n<!-- end type='" . $block->{"type"} . "'-->\n";
5678   }
5680   return $output;
5683 sub MarkDownParseLines {
5684   my ($linesref, $symbol, $context) = @_;
5685   my $output;
5686   my @lines = @$linesref;
5687   my @blocks;
5689   @blocks = &MarkDownParseBlocks (\@lines, $symbol, $context);
5690   $output = &MarkDownOutputDocBook (\@blocks, $symbol, $context);
5692   return $output;
5695 sub MarkDownParse {
5696   my ($text, $symbol) = @_;
5697   my @lines;
5699   # take out some variability in line endings
5700   $text =~ s%\r\n%\n%g;
5701   $text =~ s%\r%\n%g;
5703   # split lines
5704   @lines = split("\n", $text);
5705   $text = MarkDownParseLines(\@lines, $symbol, "");
5707   return $text;
5710 #############################################################################
5711 # LIBRARY FUNCTIONS -        These functions are used in both gtkdoc-mkdb and
5712 #                        gtkdoc-mktmpl and should eventually be moved to a
5713 #                        separate library.
5714 #############################################################################
5716 #############################################################################
5717 # Function    : ReadDeclarationsFile
5718 # Description : This reads in a file containing the function/macro/enum etc.
5719 #                declarations.
5721 #                Note that in some cases there are several declarations with
5722 #                the same name, e.g. for conditional macros. In this case we
5723 #                set a flag in the %DeclarationConditional hash so the
5724 #                declaration is not shown in the docs.
5726 #                If a macro and a function have the same name, e.g. for
5727 #                gtk_object_ref, the function declaration takes precedence.
5729 #                Some opaque structs are just declared with 'typedef struct
5730 #                _name name;' in which case the declaration may be empty.
5731 #                The structure may have been found later in the header, so
5732 #                that overrides the empty declaration.
5734 # Arguments   : $file - the declarations file to read
5735 #                $override - if declarations in this file should override
5736 #                        any current declaration.
5737 #############################################################################
5739 sub ReadDeclarationsFile {
5740     my ($file, $override) = @_;
5742     if ($override == 0) {
5743         %Declarations = ();
5744         %DeclarationTypes = ();
5745         %DeclarationConditional = ();
5746         %DeclarationOutput = ();
5747     }
5749     open (INPUT, $file)
5750         || die "Can't open $file: $!";
5751     my $declaration_type = "";
5752     my $declaration_name;
5753     my $declaration;
5754     my $is_deprecated = 0;
5755     while (<INPUT>) {
5756         if (!$declaration_type) {
5757             if (m/^<([^>]+)>/) {
5758                 $declaration_type = $1;
5759                 $declaration_name = "";
5760                 @TRACE@("Found declaration: $declaration_type\n");
5761                 $declaration = "";
5762             }
5763         } else {
5764             if (m%^<NAME>(.*)</NAME>%) {
5765                 $declaration_name = $1;
5766             } elsif (m%^<DEPRECATED/>%) {
5767                 $is_deprecated = 1;
5768             } elsif (m%^</$declaration_type>%) {
5769                 @TRACE@("Found end of declaration: $declaration_name\n");
5770                 # Check that the declaration has a name
5771                 if ($declaration_name eq "") {
5772                     &LogWarning ($file, $., "$declaration_type has no name.\n");
5773                 }
5775                 # If the declaration is an empty typedef struct _XXX XXX
5776                 # set the flag to indicate the struct has a typedef.
5777                 if (($declaration_type eq 'STRUCT' || $declaration_type eq 'UNION')
5778                     && $declaration =~ m/^\s*$/) {
5779                     @TRACE@("Struct has typedef: $declaration_name\n");
5780                     $StructHasTypedef{$declaration_name} = 1;
5781                 }
5783                 # Check if the symbol is already defined.
5784                 if (defined ($Declarations{$declaration_name})
5785                     && $override == 0) {
5786                     # Function declarations take precedence.
5787                     if ($DeclarationTypes{$declaration_name} eq 'FUNCTION') {
5788                         # Ignore it.
5789                     } elsif ($declaration_type eq 'FUNCTION') {
5790                         if ($is_deprecated) {
5791                             $Deprecated{$declaration_name} = "";
5792                         }
5793                         $Declarations{$declaration_name} = $declaration;
5794                         $DeclarationTypes{$declaration_name} = $declaration_type;
5795                     } elsif ($DeclarationTypes{$declaration_name}
5796                               eq $declaration_type) {
5797                         # If the existing declaration is empty, or is just a
5798                         # forward declaration of a struct, override it.
5799                         if ($declaration_type eq 'STRUCT' || $declaration_type eq 'UNION') {
5800                             if ($Declarations{$declaration_name} =~ m/^\s*((struct|union)\s+\w+\s*;)?\s*$/) {
5801                                 if ($is_deprecated) {
5802                                     $Deprecated{$declaration_name} = "";
5803                                 }
5804                                 $Declarations{$declaration_name} = $declaration;
5805                             } elsif ($declaration =~ m/^\s*((struct|union)\s+\w+\s*;)?\s*$/) {
5806                                 # Ignore an empty or forward declaration.
5807                             } else {
5808                                 &LogWarning ($file, $., "Structure $declaration_name has multiple definitions.");
5809                             }
5810                         } else {
5811                             # set flag in %DeclarationConditional hash for
5812                             # multiply defined macros/typedefs.
5813                             $DeclarationConditional{$declaration_name} = 1;
5814                         }
5815                     } else {
5816                         &LogWarning ($file, $., "$declaration_name has multiple definitions.");
5817                     }
5818                 } else {
5819                     if ($is_deprecated) {
5820                         $Deprecated{$declaration_name} = "";
5821                     }
5822                     $Declarations{$declaration_name} = $declaration;
5823                     $DeclarationTypes{$declaration_name} = $declaration_type;
5824                 }
5826                 $declaration_type = "";
5827                 $is_deprecated = 0;
5828             } else {
5829                 $declaration .= $_;
5830             }
5831         }
5832     }
5833     close (INPUT);
5837 #############################################################################
5838 # Function    : ReadSignalsFile
5839 # Description : This reads in an existing file which contains information on
5840 #                all GTK signals. It creates the arrays @SignalNames and
5841 #                @SignalPrototypes containing info on the signals. The first
5842 #                line of the SignalPrototype is the return type of the signal
5843 #                handler. The remaining lines are the parameters passed to it.
5844 #                The last parameter, "gpointer user_data" is always the same
5845 #                so is not included.
5846 # Arguments   : $file - the file containing the signal handler prototype
5847 #                        information.
5848 #############################################################################
5850 sub ReadSignalsFile {
5851     my ($file) = @_;
5853     my $in_signal = 0;
5854     my $signal_object;
5855     my $signal_name;
5856     my $signal_returns;
5857     my $signal_flags;
5858     my $signal_prototype;
5860     # Reset the signal info.
5861     @SignalObjects = ();
5862     @SignalNames = ();
5863     @SignalReturns = ();
5864     @SignalFlags = ();
5865     @SignalPrototypes = ();
5867     if (! -f $file) {
5868         return;
5869     }
5870     if (!open (INPUT, $file)) {
5871         warn "Can't open $file - skipping signals\n";
5872         return;
5873     }
5874     while (<INPUT>) {
5875         if (!$in_signal) {
5876             if (m/^<SIGNAL>/) {
5877                 $in_signal = 1;
5878                 $signal_object = "";
5879                 $signal_name = "";
5880                 $signal_returns = "";
5881                 $signal_prototype = "";
5882             }
5883         } else {
5884             if (m/^<NAME>(.*)<\/NAME>/) {
5885                 $signal_name = $1;
5886                 if ($signal_name =~ m/^(.*)::(.*)$/) {
5887                     $signal_object = $1;
5888                     ($signal_name = $2) =~ s/_/-/g;
5889                     @TRACE@("Found signal: $signal_name\n");
5890                 } else {
5891                     &LogWarning ($file, $., "Invalid signal name: $signal_name.");
5892                 }
5893             } elsif (m/^<RETURNS>(.*)<\/RETURNS>/) {
5894                 $signal_returns = $1;
5895             } elsif (m/^<FLAGS>(.*)<\/FLAGS>/) {
5896                 $signal_flags = $1;
5897             } elsif (m%^</SIGNAL>%) {
5898                 @TRACE@("Found end of signal: ${signal_object}::${signal_name}\nReturns: ${signal_returns}\n${signal_prototype}");
5899                 push (@SignalObjects, $signal_object);
5900                 push (@SignalNames, $signal_name);
5901                 push (@SignalReturns, $signal_returns);
5902                 push (@SignalFlags, $signal_flags);
5903                 push (@SignalPrototypes, $signal_prototype);
5904                 $in_signal = 0;
5905             } else {
5906                 $signal_prototype .= $_;
5907             }
5908         }
5909     }
5910     close (INPUT);
5914 #############################################################################
5915 # Function    : ReadTemplateFile
5916 # Description : This reads in the manually-edited documentation file
5917 #               corresponding to the file currently being created, so we can
5918 #               insert the documentation at the appropriate places.
5919 #               It outputs %SymbolTypes, %SymbolDocs and %SymbolParams, which
5920 #               is a hash of arrays.
5921 #               NOTE: This function is duplicated in gtkdoc-mktmpl (but
5922 #               slightly different).
5923 # Arguments   : $docsfile - the template file to read in.
5924 #               $skip_unused_params - 1 if the unused parameters should be
5925 #                 skipped.
5926 #############################################################################
5928 sub ReadTemplateFile {
5929     my ($docsfile, $skip_unused_params) = @_;
5931     my $template = "$docsfile.sgml";
5932     if (! -f $template) {
5933         @TRACE@("File doesn't exist: $template\n");
5934         return 0;
5935     }
5937     # start with empty hashes, we merge the source comment for each file
5938     # afterwards
5939     %SymbolDocs = ();
5940     %SymbolTypes = ();
5941     %SymbolParams = ();
5943     my $current_type = "";        # Type of symbol being read.
5944     my $current_symbol = "";        # Name of symbol being read.
5945     my $symbol_doc = "";                # Description of symbol being read.
5946     my @params;                        # Parameter names and descriptions of current
5947                                 #   function/macro/function typedef.
5948     my $current_param = -1;        # Index of parameter currently being read.
5949                                 #   Note that the param array contains pairs
5950                                 #   of param name & description.
5951     my $in_unused_params = 0;        # True if we are reading in the unused params.
5952     my $in_deprecated = 0;
5953     my $in_since = 0;
5954     my $in_stability = 0;
5956     open (DOCS, "$template")
5957         || die "Can't open $template: $!";
5959     @TRACE@("reading template $template");
5961     while (<DOCS>) {
5962         if (m/^<!-- ##### ([A-Z_]+) (\S+) ##### -->/) {
5963             my $type = $1;
5964             my $symbol = $2;
5965             if ($symbol eq "Title"
5966                 || $symbol eq "Short_Description"
5967                 || $symbol eq "Long_Description"
5968                 || $symbol eq "See_Also"
5969                 || $symbol eq "Stability_Level"
5970                 || $symbol eq "Include"
5971                 || $symbol eq "Image") {
5973                 $symbol = $docsfile . ":" . $symbol;
5974             }
5976             @TRACE@("Found symbol: $symbol\n");
5977             # Remember file and line for the symbol
5978             $SymbolSourceFile{$symbol} = $template;
5979             $SymbolSourceLine{$symbol} = $.;
5981             # Store previous symbol, but remove any trailing blank lines.
5982             if ($current_symbol ne "") {
5983                 $symbol_doc =~ s/\s+$//;
5984                 $SymbolTypes{$current_symbol} = $current_type;
5985                 $SymbolDocs{$current_symbol} = $symbol_doc;
5987                 # Check that the stability level is valid.
5988                 if ($StabilityLevel{$current_symbol}) {
5989                     $StabilityLevel{$current_symbol} = &ParseStabilityLevel($StabilityLevel{$current_symbol}, $template, $., "Stability level for $current_symbol");
5990                 }
5992                 if ($current_param >= 0) {
5993                     $SymbolParams{$current_symbol} = [ @params ];
5994                 } else {
5995                     # Delete any existing params in case we are overriding a
5996                     # previously read template.
5997                     delete $SymbolParams{$current_symbol};
5998                 }
5999             }
6000             $current_type = $type;
6001             $current_symbol = $symbol;
6002             $current_param = -1;
6003             $in_unused_params = 0;
6004             $in_deprecated = 0;
6005             $in_since = 0;
6006             $in_stability = 0;
6007             $symbol_doc = "";
6008             @params = ();
6010         } elsif (m/^<!-- # Unused Parameters # -->/) {
6011             @TRACE@("Found unused parameters\n");
6012             $in_unused_params = 1;
6013             next;
6015         } elsif ($in_unused_params && $skip_unused_params) {
6016             # When outputting the DocBook we skip unused parameters.
6017             @TRACE@("Skipping unused param: $_");
6018             next;
6020         } else {
6021             # Check if param found. Need to handle "..." and "format...".
6022             if (s/^\@([\w\.]+):\040?//) {
6023                 my $param_name = $1;
6024                 my $param_desc = $_;
6025                 # Allow variations of 'Returns'
6026                 if ($param_name =~ m/^[Rr]eturns?$/) {
6027                     $param_name = "Returns";
6028                 }
6029                 # Allow varargs variations
6030                 if ($param_name =~ m/^.*\.\.\.$/) {
6031                     $param_name = "...";
6032                 }
6034                 # strip trailing whitespaces and blank lines
6035                 s/\s+\n$/\n/m;
6036                 s/\n+$/\n/sm;
6037                 @TRACE@("Found param for symbol $current_symbol : '$param_name'= '$_'");
6039                 if ($param_name eq "Deprecated") {
6040                     $in_deprecated = 1;
6041                     $Deprecated{$current_symbol} = $_;
6042                 } elsif ($param_name eq "Since") {
6043                     $in_since = 1;
6044                     chomp;
6045                     $Since{$current_symbol} = $_;
6046                 } elsif ($param_name eq "Stability") {
6047                     $in_stability = 1;
6048                     $StabilityLevel{$current_symbol} = $_;
6049                 } else {
6050                     push (@params, $param_name);
6051                     push (@params, $param_desc);
6052                     $current_param += $PARAM_FIELD_COUNT;
6053                 }
6054             } else {
6055                 # strip trailing whitespaces and blank lines
6056                 s/\s+\n$/\n/m;
6057                 s/\n+$/\n/sm;
6059                 if (!m/^\s+$/) {
6060                     if ($in_deprecated) {
6061                         $Deprecated{$current_symbol} .= $_;
6062                     } elsif ($in_since) {
6063                         &LogWarning ($template, $., "multi-line since docs found");
6064                         #$Since{$current_symbol} .= $_;
6065                     } elsif ($in_stability) {
6066                         $StabilityLevel{$current_symbol} .= $_;
6067                     } elsif ($current_param >= 0) {
6068                         $params[$current_param] .= $_;
6069                     } else {
6070                         $symbol_doc .= $_;
6071                     }
6072                 }
6073             }
6074         }
6075     }
6077     # Remember to finish the current symbol doccs.
6078     if ($current_symbol ne "") {
6080         $symbol_doc =~ s/\s+$//;
6081         $SymbolTypes{$current_symbol} = $current_type;
6082         $SymbolDocs{$current_symbol} = $symbol_doc;
6084         # Check that the stability level is valid.
6085         if ($StabilityLevel{$current_symbol}) {
6086             $StabilityLevel{$current_symbol} = &ParseStabilityLevel($StabilityLevel{$current_symbol}, $template, $., "Stability level for $current_symbol");
6087         }
6089         if ($current_param >= 0) {
6090             $SymbolParams{$current_symbol} = [ @params ];
6091         } else {
6092             # Delete any existing params in case we are overriding a
6093             # previously read template.
6094             delete $SymbolParams{$current_symbol};
6095         }
6096     }
6098     close (DOCS);
6099     return 1;
6103 #############################################################################
6104 # Function    : ReadObjectHierarchy
6105 # Description : This reads in the $MODULE-hierarchy.txt file containing all
6106 #               the GtkObject subclasses described in this module (and their
6107 #               ancestors).
6108 #               It places them in the @Objects array, and places their level
6109 #               in the object hierarchy in the @ObjectLevels array, at the
6110 #               same index. GtkObject, the root object, has a level of 1.
6112 #               This also generates tree_index.sgml as it goes along.
6114 # Arguments   : none
6115 #############################################################################
6117 sub ReadObjectHierarchy {
6118     @Objects = ();
6119     @ObjectLevels = ();
6121     if (! -f $OBJECT_TREE_FILE) {
6122         return;
6123     }
6124     if (!open (INPUT, $OBJECT_TREE_FILE)) {
6125         warn "Can't open $OBJECT_TREE_FILE - skipping object tree\n";
6126         return;
6127     }
6129     # Only emit objects if they are supposed to be documented, or if
6130     # they have documented children. To implement this, we maintain a
6131     # stack of pending objects which will be emitted if a documented
6132     # child turns up.
6133     my @pending_objects = ();
6134     my @pending_levels = ();
6135     my $root;
6136     my @tree = ();
6137     while (<INPUT>) {
6138         if (m/\S+/) {
6139             my $object = $&;
6140             my $level = (length($`)) / 2 + 1;
6141             my $xref = "";
6143             if ($level == 1) {
6144                 $root = $object;
6145             }
6147             while (($#pending_levels >= 0) && ($pending_levels[$#pending_levels] >= $level)) {
6148                 my $pobject = pop(@pending_objects);
6149                 my $plevel = pop(@pending_levels);
6150             }
6152             push (@pending_objects, $object);
6153             push (@pending_levels, $level);
6155             if (exists($KnownSymbols{$object})) {
6156                 while ($#pending_levels >= 0) {
6157                     $object = shift @pending_objects;
6158                     $level = shift @pending_levels;
6159                     $xref = &MakeXRef ($object);
6161                     push (@tree, ' ' x ($level * 4) . "$xref");
6162                     push (@Objects, $object);
6163                     push (@ObjectLevels, $level);
6164                     $ObjectRoots{$object} = $root;
6165                 }
6166             }
6167             #else {
6168             #    LogWarning ($OBJECT_TREE_FILE, $., "unknown type $object");
6169             #}
6170         }
6171     }
6172     close (INPUT);
6174     # FIXME: use xml
6175     # my $old_tree_index = "$DB_OUTPUT_DIR/tree_index.$xml";
6176     my $old_tree_index = "$DB_OUTPUT_DIR/tree_index.sgml";
6177     my $new_tree_index = "$DB_OUTPUT_DIR/tree_index.new";
6179     open (OUTPUT, ">$new_tree_index")
6180         || die "Can't create $new_tree_index: $!";
6182     print (OUTPUT &MakeDocHeader ("screen")."\n<screen>\n".&AddTreeLineArt(\@tree)."\n</screen>\n");
6183     close (OUTPUT);
6185     &UpdateFileIfChanged ($old_tree_index, $new_tree_index, 0);
6187     &OutputObjectList;
6190 #############################################################################
6191 # Function    : ReadInterfaces
6192 # Description : This reads in the $MODULE.interfaces file.
6194 # Arguments   : none
6195 #############################################################################
6197 sub ReadInterfaces {
6198     %Interfaces = ();
6200     if (! -f $INTERFACES_FILE) {
6201         return;
6202     }
6203     if (!open (INPUT, $INTERFACES_FILE)) {
6204         warn "Can't open $INTERFACES_FILE - skipping interfaces\n";
6205         return;
6206     }
6208     while (<INPUT>) {
6209        chomp;
6210        my ($object, @ifaces) = split;
6211        if (exists($KnownSymbols{$object}) && $KnownSymbols{$object} == 1) {
6212            my @knownIfaces = ();
6214            # filter out private interfaces, but leave foreign interfaces
6215            foreach my $iface (@ifaces) {
6216                if (!exists($KnownSymbols{$iface}) || $KnownSymbols{$iface} == 1) {
6217                    push (@knownIfaces, $iface);
6218                }
6219              }
6221            $Interfaces{$object} = join(' ', @knownIfaces);
6222            @TRACE@("Interfaces for $object: $Interfaces{$object}\n");
6223        } else {
6224          @TRACE@("skipping interfaces for unknown symbol: $object\n");
6225        }
6226     }
6227     close (INPUT);
6230 #############################################################################
6231 # Function    : ReadPrerequisites
6232 # Description : This reads in the $MODULE.prerequisites file.
6234 # Arguments   : none
6235 #############################################################################
6237 sub ReadPrerequisites {
6238     %Prerequisites = ();
6240     if (! -f $PREREQUISITES_FILE) {
6241         return;
6242     }
6243     if (!open (INPUT, $PREREQUISITES_FILE)) {
6244         warn "Can't open $PREREQUISITES_FILE - skipping prerequisites\n";
6245         return;
6246     }
6248     while (<INPUT>) {
6249        chomp;
6250        my ($iface, @prereqs) = split;
6251        if (exists($KnownSymbols{$iface}) && $KnownSymbols{$iface} == 1) {
6252            my @knownPrereqs = ();
6254            # filter out private prerequisites, but leave foreign prerequisites
6255            foreach my $prereq (@prereqs) {
6256                if (!exists($KnownSymbols{$prereq}) || $KnownSymbols{$prereq} == 1) {
6257                   push (@knownPrereqs, $prereq);
6258                }
6259            }
6261            $Prerequisites{$iface} = join(' ', @knownPrereqs);
6262        }
6263     }
6264     close (INPUT);
6267 #############################################################################
6268 # Function    : ReadArgsFile
6269 # Description : This reads in an existing file which contains information on
6270 #                all GTK args. It creates the arrays @ArgObjects, @ArgNames,
6271 #                @ArgTypes, @ArgFlags, @ArgNicks and @ArgBlurbs containing info
6272 #               on the args.
6273 # Arguments   : $file - the file containing the arg information.
6274 #############################################################################
6276 sub ReadArgsFile {
6277     my ($file) = @_;
6279     my $in_arg = 0;
6280     my $arg_object;
6281     my $arg_name;
6282     my $arg_type;
6283     my $arg_flags;
6284     my $arg_nick;
6285     my $arg_blurb;
6286     my $arg_default;
6287     my $arg_range;
6289     # Reset the args info.
6290     @ArgObjects = ();
6291     @ArgNames = ();
6292     @ArgTypes = ();
6293     @ArgFlags = ();
6294     @ArgNicks = ();
6295     @ArgBlurbs = ();
6296     @ArgDefaults = ();
6297     @ArgRanges = ();
6299     if (! -f $file) {
6300         return;
6301     }
6302     if (!open (INPUT, $file)) {
6303         warn "Can't open $file - skipping args\n";
6304         return;
6305     }
6306     while (<INPUT>) {
6307         if (!$in_arg) {
6308             if (m/^<ARG>/) {
6309                 $in_arg = 1;
6310                 $arg_object = "";
6311                 $arg_name = "";
6312                 $arg_type = "";
6313                 $arg_flags = "";
6314                 $arg_nick = "";
6315                 $arg_blurb = "";
6316                 $arg_default = "";
6317                 $arg_range = "";
6318             }
6319         } else {
6320             if (m/^<NAME>(.*)<\/NAME>/) {
6321                 $arg_name = $1;
6322                 if ($arg_name =~ m/^(.*)::(.*)$/) {
6323                     $arg_object = $1;
6324                     ($arg_name = $2) =~ s/_/-/g;
6325                     @TRACE@("Found arg: $arg_name\n");
6326                 } else {
6327                     &LogWarning ($file, $., "Invalid argument name: $arg_name");
6328                 }
6329             } elsif (m/^<TYPE>(.*)<\/TYPE>/) {
6330                 $arg_type = $1;
6331             } elsif (m/^<RANGE>(.*)<\/RANGE>/) {
6332                 $arg_range = $1;
6333             } elsif (m/^<FLAGS>(.*)<\/FLAGS>/) {
6334                 $arg_flags = $1;
6335             } elsif (m/^<NICK>(.*)<\/NICK>/) {
6336                 $arg_nick = $1;
6337             } elsif (m/^<BLURB>(.*)<\/BLURB>/) {
6338                 $arg_blurb = $1;
6339                 if ($arg_blurb eq "(null)") {
6340                   $arg_blurb = "";
6341                   &LogWarning ($file, $., "Property ${arg_object}:${arg_name} has no documentation.");
6342                 }
6343             } elsif (m/^<DEFAULT>(.*)<\/DEFAULT>/) {
6344                 $arg_default = $1;
6345             } elsif (m%^</ARG>%) {
6346                 @TRACE@("Found end of arg: ${arg_object}::${arg_name}\n${arg_type} : ${arg_flags}\n");
6347                 push (@ArgObjects, $arg_object);
6348                 push (@ArgNames, $arg_name);
6349                 push (@ArgTypes, $arg_type);
6350                 push (@ArgRanges, $arg_range);
6351                 push (@ArgFlags, $arg_flags);
6352                 push (@ArgNicks, $arg_nick);
6353                 push (@ArgBlurbs, $arg_blurb);
6354                 push (@ArgDefaults, $arg_default);
6355                 $in_arg = 0;
6356             }
6357         }
6358     }
6359     close (INPUT);
6362 #############################################################################
6363 # Function    : AddTreeLineArt
6364 # Description : Add unicode lineart to a pre-indented string array and returns
6365 #               it as as multiline string.
6366 # Arguments   : @tree - array of indented strings.
6367 #############################################################################
6369 sub AddTreeLineArt {
6370   my @tree = @{$_[0]};
6371   my $i;
6372   my $j;
6373   my $indent;
6375   # iterate bottom up over the tree
6376   for ($i = $#tree; $i >= 0; $i--) {
6377     # count leading spaces
6378     $tree[$i] =~ /^([^<A-Za-z]*)/;
6379     $indent = length( $1 );
6380     # replace with ╰───, if place of ╰ is not space insert ├
6381     if ($indent > 4) {
6382       if (substr($tree[$i],$indent-4,1) eq " ") {
6383         substr($tree[$i],$indent-4,4) = "--- ";
6384       } else {
6385         substr($tree[$i],$indent-4,4) = "+-- ";
6386       }
6387       # go lines up while space and insert |
6388       for ($j = $i - 1; ($j >= 0 && substr($tree[$j],$indent-4,1) eq ' '); $j--) {
6389         substr($tree[$j],$indent-4,1) = '|';
6390       }
6391     }
6392   }
6394   my $res = join("\n", @tree);
6395   # unicode chars for: ╰──
6396   $res =~ s%---%<phrase role=\"lineart\">&#9584;&#9472;&#9472;</phrase>%g;
6397   # unicde chars for: ├──
6398   $res =~ s%\+--%<phrase role=\"lineart\">&#9500;&#9472;&#9472;</phrase>%g;
6399   # unicode char for: │
6400   $res =~ s%\|%<phrase role=\"lineart\">&#9474;</phrase>%g;
6402   return $res;
6406 #############################################################################
6407 # Function    : CheckIsObject
6408 # Description : Returns 1 if the given name is a GObject or a subclass.
6409 #                It uses the global @Objects array.
6410 #                Note that the @Objects array only contains classes in the
6411 #                current module and their ancestors - not all GObject classes.
6412 # Arguments   : $name - the name to check.
6413 #############################################################################
6415 sub CheckIsObject {
6416     my ($name) = @_;
6417     my $root = $ObjectRoots{$name};
6418     # Let GBoxed pass as an object here to get -struct appended to the id
6419     # and prevent conflicts with sections.
6420     return (defined($root) and $root ne 'GEnum' and $root ne 'GFlags');
6424 #############################################################################
6425 # Function    : MakeReturnField
6426 # Description : Pads a string to $RETURN_TYPE_FIELD_WIDTH.
6427 # Arguments   : $str - the string to pad.
6428 #############################################################################
6430 sub MakeReturnField {
6431     my ($str) = @_;
6433     return $str . (' ' x ($RETURN_TYPE_FIELD_WIDTH - length ($str)));
6436 #############################################################################
6437 # Function    : GetSymbolSourceFile
6438 # Description : Get the filename where the symbol docs where taken from.
6439 # Arguments   : $symbol - the symbol name
6440 #############################################################################
6442 sub GetSymbolSourceFile {
6443     my ($symbol) = @_;
6445     if (defined($SourceSymbolSourceFile{$symbol})) {
6446         return $SourceSymbolSourceFile{$symbol};
6447     } elsif (defined($SymbolSourceFile{$symbol})) {
6448         return $SymbolSourceFile{$symbol};
6449     } else {
6450         return "";
6451     }
6454 #############################################################################
6455 # Function    : GetSymbolSourceLine
6456 # Description : Get the file line where the symbol docs where taken from.
6457 # Arguments   : $symbol - the symbol name
6458 #############################################################################
6460 sub GetSymbolSourceLine {
6461     my ($symbol) = @_;
6463     if (defined($SourceSymbolSourceLine{$symbol})) {
6464         return $SourceSymbolSourceLine{$symbol};
6465     } elsif (defined($SymbolSourceLine{$symbol})) {
6466         return $SymbolSourceLine{$symbol};
6467     } else {
6468         return 0;
6469     }