mkdb: add support for multiline symbol annotations
[gtk-doc.git] / gtkdoc-mkdb.in
blobe736fb77ed642906dffa468ec12ce8edf0582c42
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 $SGML_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;
56 my %optctl = ('module' => \$MODULE,
57               'source-dir' => \@SOURCE_DIRS,
58               'source-suffixes' => \$SOURCE_SUFFIXES,
59               'ignore-files' => \$IGNORE_FILES,
60               'output-dir' => \$SGML_OUTPUT_DIR,
61               'tmpl-dir' => \$TMPL_DIR,
62               'version' => \$PRINT_VERSION,
63               'help' => \$PRINT_HELP,
64               'main-sgml-file' => \$MAIN_SGML_FILE,
65               'expand-content-files' => \$EXPAND_CONTENT_FILES,
66               'sgml-mode' => \$INLINE_MARKUP_MODE,
67               'xml-mode' => \$INLINE_MARKUP_MODE,
68               'default-stability' => \$DEFAULT_STABILITY,
69               'default-includes' => \$DEFAULT_INCLUDES,
70               'output-format' => \$OUTPUT_FORMAT,
71               'name-space' => \$NAME_SPACE,
72               'outputallsymbols' => \$OUTPUT_ALL_SYMBOLS,
73               'outputsymbolswithoutsince' => \$OUTPUT_SYMBOLS_WITHOUT_SINCE
74               );
75 GetOptions(\%optctl, "module=s", "source-dir:s", "source-suffixes:s",
76     "ignore-files:s", "output-dir:s", "tmpl-dir:s", "version", "outputallsymbols",
77     "outputsymbolswithoutsince",
78     "expand-content-files:s", "main-sgml-file:s", "extra-db-files:s", "help",
79     "sgml-mode", "xml-mode", "default-stability:s", "default-includes:s",
80     "output-format:s", "name-space:s");
82 if ($PRINT_VERSION) {
83     print "@VERSION@\n";
84     exit 0;
87 if (!$MODULE) {
88     $PRINT_HELP = 1;
91 if ($DEFAULT_STABILITY && $DEFAULT_STABILITY ne "Stable"
92     && $DEFAULT_STABILITY ne "Private" && $DEFAULT_STABILITY ne "Unstable") {
93     $PRINT_HELP = 1;
96 if ($PRINT_HELP) {
97     print <<EOF;
98 gtkdoc-mkdb version @VERSION@ - generate docbook files
100 --module=MODULE_NAME       Name of the doc module being parsed
101 --source-dir=DIRNAME       Directories which contain inline reference material
102 --source-suffixes=SUFFIXES Suffixes of source files to scan, comma-separated
103 --ignore-files=FILES       A space-separated list of header files/dirs not to
104                            scan
105 --output-dir=DIRNAME       Directory to put the generated DocBook files in
106 --tmpl-dir=DIRNAME         Directory in which template files may be found
107 --main-sgml-file=FILE      File containing the toplevel DocBook file.
108 --expand-content-files=FILES Extra DocBook files to expand abbreviations in.
109 --output-format=FORMAT     Format to use for the generated docbook, XML or SGML.
110 --{xml,sgml}-mode          Allow DocBook markup in inline documentation.
111 --default-stability=LEVEL  Specify default stability Level. Valid values are
112                            Stable, Unstable, or Private.
113 --default-includes=FILENAMES Specify default includes for section Synopsis
114 --name-space=NS            Omit namespace in index.
115 --version                  Print the version of this program
116 --help                     Print this help
118     exit 0;
121 @TRACE@(" ignore files: [$IGNORE_FILES]\n");
123 my ($empty_element_end, $doctype_header);
125 # autodetect output format
126 if (! defined($OUTPUT_FORMAT) || ($OUTPUT_FORMAT eq "")) {
127     if (!$MAIN_SGML_FILE) {
128         if (-e "${MODULE}-docs.xml") {
129             $OUTPUT_FORMAT = "xml";
130         } else {
131             $OUTPUT_FORMAT = "sgml";
132         }
133     } else {
134         if ($MAIN_SGML_FILE =~ m/.*\.(.*ml)$/i) {
135             $OUTPUT_FORMAT = lc($1);
136         }
137     }
139 } else {
140     $OUTPUT_FORMAT = lc($OUTPUT_FORMAT);
143 @TRACE@(" output-format: [$OUTPUT_FORMAT]\n");
145 if ($OUTPUT_FORMAT eq "xml") {
146     if (!$MAIN_SGML_FILE) {
147         # backwards compatibility
148         if (-e "${MODULE}-docs.sgml") {
149             $MAIN_SGML_FILE = "${MODULE}-docs.sgml";
150         } else {
151             $MAIN_SGML_FILE = "${MODULE}-docs.xml";
152         }
153     }
154     $empty_element_end = "/>";
156     if (-e $MAIN_SGML_FILE) {
157         open(INPUT, "<$MAIN_SGML_FILE") || die "Can't open $MAIN_SGML_FILE";
158         $doctype_header = "";
159         while (<INPUT>) {
160             if (/^\s*<(book|chapter|article)/) {
161                 # check that the top-level tag or the doctype decl contain the xinclude namespace decl
162                 if (($_ !~ m/http:\/\/www.w3.org\/200[13]\/XInclude/) && ($doctype_header !~ m/http:\/\/www.w3.org\/200[13]\/XInclude/m)) {
163                     $doctype_header = "";
164                 }
165                 last;
166             }
167             $doctype_header .= $_;
168         }
169         close(INPUT);
170         $doctype_header =~ s/<!DOCTYPE \w+/<!DOCTYPE refentry/;
171         # if there are SYSTEM ENTITIES here, we should prepend "../" to the path
172         # FIXME: not sure if we can do this now, as people already work-around the problem
173         # $doctype_header =~ s#<!ENTITY % ([a-zA-Z-]+) SYSTEM \"([^/][a-zA-Z./]+)\">#<!ENTITY % $1 SYSTEM \"../$2\">#g;
174     } else {
175         $doctype_header =
176 "<?xml version=\"1.0\"?>\n" .
177 "<!DOCTYPE refentry PUBLIC \"-//OASIS//DTD DocBook XML V4.3//EN\"\n" .
178 "               \"http://www.oasis-open.org/docbook/xml/4.3/docbookx.dtd\"\n" .
179 "[\n" .
180 "  <!ENTITY % local.common.attrib \"xmlns:xi  CDATA  #FIXED 'http://www.w3.org/2003/XInclude'\">\n" .
181 "]>\n";
182     }
183 } else {
184     if (!$MAIN_SGML_FILE) {
185         $MAIN_SGML_FILE = "${MODULE}-docs.sgml";
186     }
187     $empty_element_end = ">";
188     $doctype_header = "";
191 my $ROOT_DIR = ".";
193 # All the files are written in subdirectories beneath here.
194 $TMPL_DIR = $TMPL_DIR ? $TMPL_DIR : "$ROOT_DIR/tmpl";
196 # This is where we put all the DocBook output.
197 $SGML_OUTPUT_DIR = $SGML_OUTPUT_DIR ? $SGML_OUTPUT_DIR : "$ROOT_DIR/$OUTPUT_FORMAT";
199 # This file contains the object hierarchy.
200 my $OBJECT_TREE_FILE = "$ROOT_DIR/$MODULE.hierarchy";
202 # This file contains the interfaces.
203 my $INTERFACES_FILE = "$ROOT_DIR/$MODULE.interfaces";
205 # This file contains the prerequisites.
206 my $PREREQUISITES_FILE = "$ROOT_DIR/$MODULE.prerequisites";
208 # This file contains signal arguments and names.
209 my $SIGNALS_FILE = "$ROOT_DIR/$MODULE.signals";
211 # The file containing Arg information.
212 my $ARGS_FILE = "$ROOT_DIR/$MODULE.args";
214 # These global arrays store information on signals. Each signal has an entry
215 # in each of these arrays at the same index, like a multi-dimensional array.
216 my @SignalObjects;        # The GtkObject which emits the signal.
217 my @SignalNames;        # The signal name.
218 my @SignalReturns;        # The return type.
219 my @SignalFlags;        # Flags for the signal
220 my @SignalPrototypes;        # The rest of the prototype of the signal handler.
222 # These global arrays store information on Args. Each Arg has an entry
223 # in each of these arrays at the same index, like a multi-dimensional array.
224 my @ArgObjects;                # The GtkObject which has the Arg.
225 my @ArgNames;                # The Arg name.
226 my @ArgTypes;                # The Arg type - gint, GtkArrowType etc.
227 my @ArgFlags;                # How the Arg can be used - readable/writable etc.
228 my @ArgNicks;                # The nickname of the Arg.
229 my @ArgBlurbs;          # Docstring of the Arg.
230 my @ArgDefaults;        # Default value of the Arg.
231 my @ArgRanges;                # The range of the Arg type
232 # These global hashes store declaration info keyed on a symbol name.
233 my %Declarations;
234 my %DeclarationTypes;
235 my %DeclarationConditional;
236 my %DeclarationOutput;
237 my %Deprecated;
238 my %Since;
239 my %StabilityLevel;
240 my %StructHasTypedef;
242 # These global hashes store the existing documentation.
243 my %SymbolDocs;
244 my %SymbolTypes;
245 my %SymbolParams;
246 my %SymbolSourceFile;
247 my %SymbolSourceLine;
248 my %SymbolAnnotations;
250 # These global hashes store documentation scanned from the source files.
251 my %SourceSymbolDocs;
252 my %SourceSymbolParams;
253 my %SourceSymbolSourceFile;
254 my %SourceSymbolSourceLine;
256 # all documentation goes in here, so we can do coverage analysis
257 my %AllSymbols;
258 my %AllIncompleteSymbols;
259 my %AllUnusedSymbols;
260 my %AllDocumentedSymbols;
262 # Undeclared yet documented symbols
263 my %UndeclaredSymbols;
265 # These global arrays store GObject, subclasses and the hierarchy (also of
266 # non-object derived types).
267 my @Objects;
268 my @ObjectLevels;
269 my %ObjectRoots;
271 my %Interfaces;
272 my %Prerequisites;
274 # holds the symbols which are mentioned in $MODULE-sections.txt and in which
275 # section they are defined
276 my %KnownSymbols;
277 my %SymbolSection;
278 my %SymbolSectionId;
280 # collects index entries
281 my %IndexEntriesFull;
282 my %IndexEntriesSince;
283 my %IndexEntriesDeprecated;
285 # Standard C preprocessor directives, which we ignore for '#' abbreviations.
286 my %PreProcessorDirectives;
287 $PreProcessorDirectives{"assert"} = 1;
288 $PreProcessorDirectives{"define"} = 1;
289 $PreProcessorDirectives{"elif"} = 1;
290 $PreProcessorDirectives{"else"} = 1;
291 $PreProcessorDirectives{"endif"} = 1;
292 $PreProcessorDirectives{"error"} = 1;
293 $PreProcessorDirectives{"if"} = 1;
294 $PreProcessorDirectives{"ifdef"} = 1;
295 $PreProcessorDirectives{"ifndef"} = 1;
296 $PreProcessorDirectives{"include"} = 1;
297 $PreProcessorDirectives{"line"} = 1;
298 $PreProcessorDirectives{"pragma"} = 1;
299 $PreProcessorDirectives{"unassert"} = 1;
300 $PreProcessorDirectives{"undef"} = 1;
301 $PreProcessorDirectives{"warning"} = 1;
303 # remember used annotation (to write minimal glossary)
304 my %AnnotationsUsed;
306 my %AnnotationDefinition = (
307     # the GObjectIntrospection annotations are defined at:
308     # https://live.gnome.org/GObjectIntrospection/Annotations
309     'allow-none' => "NULL is OK, both for passing and for returning.",
310     'nullable' => "NULL may be passed as the value in, out, in-out; or as a return value.",
311     'optional' => "NULL may be passed instead of a pointer to a location.",
312     'array' => "Parameter points to an array of items.",
313     'attribute' => "Deprecated free-form custom annotation, replaced by (attributes) annotation.",
314     'attributes' => "Free-form key-value pairs.",
315     'closure' => "This parameter is a 'user_data', for callbacks; many bindings can pass NULL here.",
316     'constructor' => "This symbol is a constructor, not a static method.",
317     'destroy' => "This parameter is a 'destroy_data', for callbacks.",
318     'default' => "Default parameter value (for in case the <acronym>shadows</acronym>-to function has less parameters).",
319     'element-type' => "Generics and defining elements of containers and arrays.",
320     'error-domains' => "Typed errors. Similar to throws in Java.",
321     'foreign' => "This is a foreign struct.",
322     'get-value-func' => "The specified function is used to convert a struct from a GValue, must be a GTypeInstance.",
323     'in' => "Parameter for input. Default is <acronym>transfer none</acronym>.",
324     'inout' => "Parameter for input and for returning results. Default is <acronym>transfer full</acronym>.",
325     'in-out' => "Parameter for input and for returning results. Default is <acronym>transfer full</acronym>.",
326     'method' => "This is a method",
327     'not-error' => "A GError parameter is not to be handled like a normal GError.",
328     'out' => "Parameter for returning results. Default is <acronym>transfer full</acronym>.",
329     'out caller-allocates' => "Out parameter, where caller must allocate storage.",
330     'out callee-allocates' => "Out parameter, where caller must allocate storage.",
331     'ref-func' => "The specified function is used to ref a struct, must be a GTypeInstance.",
332     'rename-to' => "Rename the original symbol's name to SYMBOL.",
333     'scope call' => "The callback is valid only during the call to the method.",
334     'scope async' => "The callback is valid until first called.",
335     'scope notified' => "The callback is valid until the GDestroyNotify argument is called.",
336     'set-value-func' => "The specified function is used to convert from a struct to a GValue, must be a GTypeInstance.",
337     'skip' => "Exposed in C code, not necessarily available in other languages.",
338     'transfer container' => "Free data container after the code is done.",
339     'transfer floating' => "Alias for <acronym>transfer none</acronym>, used for objects with floating refs.",
340     'transfer full' => "Free data after the code is done.",
341     'transfer none' => "Don't free data after the code is done.",
342     'type' => "Override the parsed C type with given type.",
343     'unref-func' => "The specified function is used to unref a struct, must be a GTypeInstance.",
344     'virtual' => "This is the invoker for a virtual method.",
345     'value' => "The specified value overrides the evaluated value of the constant.",
346     # Stability Level definition
347     # https://bugzilla.gnome.org/show_bug.cgi?id=170860
348     'Stable' => <<EOF,
349 The intention of a Stable interface is to enable arbitrary third parties to
350 develop applications to these interfaces, release them, and have confidence that
351 they will run on all minor releases of the product (after the one in which the
352 interface was introduced, and within the same major release). Even at a major
353 release, incompatible changes are expected to be rare, and to have strong
354 justifications.
356     'Unstable' => <<EOF,
357 Unstable interfaces are experimental or transitional. They are typically used to
358 give outside developers early access to new or rapidly changing technology, or
359 to provide an interim solution to a problem where a more general solution is
360 anticipated. No claims are made about either source or binary compatibility from
361 one minor release to the next.
363 The Unstable interface level is a warning that these interfaces are  subject to
364 change without warning and should not be used in unbundled products.
366 Given such caveats, customer impact need not be a factor when considering
367 incompatible changes to an Unstable interface in a major or minor release.
368 Nonetheless, when such changes are introduced, the changes should still be
369 mentioned in the release notes for the affected release.
371     'Private' => <<EOF
372 An interface that can be used within the GNOME stack itself, but that is not
373 documented for end-users.  Such functions should only be used in specified and
374 documented ways.
378 # Elements to consider non-block items in MarkDown parsing
379 my %MD_TEXT_LEVEL_ELEMENTS = ( "literal" => 1,
380                                "emphasis" => 1,
381                                "envar" => 1,
382                                "filename" => 1,
383                                "firstterm" => 1,
384                                "function" => 1,
385                                "manvolnum" => 1,
386                                "option" => 1,
387                                "replaceable" => 1,
388                                "structname" => 1,
389                                "title" => 1,
390                                "varname" => 1 );
391 my %MD_ESCAPABLE_CHARS = ( "\\" => 1,
392                            "`" => 1,
393                            "*" => 1,
394                            "_" => 1,
395                            "{" => 1,
396                            "}" => 1,
397                            "[" => 1,
398                            "]" => 1,
399                            "(" => 1,
400                            ")" => 1,
401                            ">" => 1,
402                            "#" => 1,
403                            "+" => 1,
404                            "-" => 1,
405                            "." => 1,
406                            "!" => 1 );
407 my %MD_GTK_ESCAPABLE_CHARS = ( "@" => 1,
408                                "%" => 1 );
410 # Create the root DocBook output directory if it doens't exist.
411 if (! -e $SGML_OUTPUT_DIR) {
412     mkdir ("$SGML_OUTPUT_DIR", 0777)
413         || die "Can't create directory: $SGML_OUTPUT_DIR";
416 # Function and other declaration output settings.
417 my $RETURN_TYPE_FIELD_WIDTH = 20;
418 my $SYMBOL_FIELD_WIDTH = 36;
419 my $MAX_SYMBOL_FIELD_WIDTH = 40;
420 my $SIGNAL_FIELD_WIDTH = 16;
421 my $PARAM_FIELD_COUNT = 2;
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);
435 for my $dir (@SOURCE_DIRS) {
436     &ReadSourceDocumentation ($dir);
439 my $changed = &OutputSGML ("$ROOT_DIR/$MODULE-sections.txt");
441 # If any of the DocBook SGML 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);
508 #############################################################################
509 # Function    : OutputObjectList
510 # Description : This outputs the alphabetical list of objects, in a columned
511 #                table.
512 #               FIXME: Currently this also outputs ancestor objects
513 #                which may not actually be in this module.
514 # Arguments   : none
515 #############################################################################
517 sub OutputObjectList {
518     my $cols = 3;
520     # FIXME: use $OUTPUT_FORMAT
521     # my $old_object_index = "$SGML_OUTPUT_DIR/object_index.$OUTPUT_FORMAT";
522     my $old_object_index = "$SGML_OUTPUT_DIR/object_index.sgml";
523     my $new_object_index = "$SGML_OUTPUT_DIR/object_index.new";
525     open (OUTPUT, ">$new_object_index")
526         || die "Can't create $new_object_index: $!";
528     if ($OUTPUT_FORMAT eq "xml") {
529         my $header = $doctype_header;
531         $header =~ s/<!DOCTYPE \w+/<!DOCTYPE informaltable/;
532         print (OUTPUT "$header");
533     }
535     print (OUTPUT <<EOF);
536 <informaltable pgwide="1" frame="none">
537 <tgroup cols="$cols">
538 <colspec colwidth="1*"${empty_element_end}
539 <colspec colwidth="1*"${empty_element_end}
540 <colspec colwidth="1*"${empty_element_end}
541 <tbody>
544     my $count = 0;
545     my $object;
546     foreach $object (sort (@Objects)) {
547         my $xref = &MakeXRef ($object);
548         if ($count % $cols == 0) { print (OUTPUT "<row>\n"); }
549         print (OUTPUT "<entry>$xref</entry>\n");
550         if ($count % $cols == ($cols - 1)) { print (OUTPUT "</row>\n"); }
551         $count++;
552     }
553     if ($count == 0) {
554         # emit an empty row, since empty tables are invalid
555         print (OUTPUT "<row><entry> </entry></row>\n");
556     }
557     else {
558         if ($count % $cols > 0) {
559             print (OUTPUT "</row>\n");
560         }
561     }
563     print (OUTPUT <<EOF);
564 </tbody></tgroup></informaltable>
566     close (OUTPUT);
568     &UpdateFileIfChanged ($old_object_index, $new_object_index, 0);
571 #############################################################################
572 # Function    : TrimTextBlock
573 # Description : Trims extra whitespace. Empty lines inside a block are
574 #                preserved.
575 # Arguments   : $desc - the text block to trim. May contain newlines.
576 #############################################################################
578 sub TrimTextBlock {
579   my ($desc) = @_;
580   
581   # strip leading spaces on the block
582   $desc =~ s/^\s+//s;
583   # strip trailing spaces on every line
584   $desc =~ s/\s+$/\n/mg;
585   
586   return $desc;
590 #############################################################################
591 # Function    : OutputSGML
592 # Description : This collects the output for each section of the docs, and
593 #                outputs each file when the end of the section is found.
594 # Arguments   : $file - the $MODULE-sections.txt file which contains all of
595 #                the functions/macros/structs etc. being documented, organised
596 #                into sections and subsections.
597 #############################################################################
599 sub OutputSGML {
600     my ($file) = @_;
602     @TRACE@("Reading: $file\n");
603     open (INPUT, $file)
604         || die "Can't open $file: $!";
605     my $filename = "";
606     my $book_top = "";
607     my $book_bottom = "";
608     my $includes = (defined $DEFAULT_INCLUDES) ? $DEFAULT_INCLUDES : "";
609     my $section_includes = "";
610     my $in_section = 0;
611     my $title = "";
612     my $section_id = "";
613     my $subsection = "";
614     my $num_symbols;
615     my $changed = 0;
616     my $functions_synop = "";
617     my $other_synop = "";
618     my $functions_details = "";
619     my $other_details = "";
620     my $signals_synop = "";
621     my $signals_desc = "";
622     my $args_synop = "";
623     my $child_args_synop = "";
624     my $style_args_synop = "";
625     my $args_desc = "";
626     my $child_args_desc = "";
627     my $style_args_desc = "";
628     my $hierarchy_str = "";
629     my @hierarchy = ();
630     my $interfaces = "";
631     my $implementations = "";
632     my $prerequisites = "";
633     my $derived = "";
634     my @file_objects = ();
635     my %templates = ();
636     my %symbol_def_line = ();
638     # merge the source docs, in case there are no templates
639     &MergeSourceDocumentation;
641     while (<INPUT>) {
642         if (m/^#/) {
643             next;
645         } elsif (m/^<SECTION>/) {
646             $num_symbols = 0;
647             $in_section = 1;
648             @file_objects = ();
649             %symbol_def_line = ();
651         } elsif (m/^<SUBSECTION\s*(.*)>/i) {
652             $other_synop .= "\n";
653             $functions_synop .= "\n";
654             $subsection = $1;
656         } elsif (m/^<SUBSECTION>/) {
658         } elsif (m/^<TITLE>(.*)<\/TITLE>/) {
659             $title = $1;
660             @TRACE@("Section: $title\n");
662             # We don't want warnings if object & class structs aren't used.
663             $DeclarationOutput{$title} = 1;
664             $DeclarationOutput{"${title}Class"} = 1;
665             $DeclarationOutput{"${title}Iface"} = 1;
666             $DeclarationOutput{"${title}Interface"} = 1;
668         } elsif (m/^<FILE>(.*)<\/FILE>/) {
669             $filename = $1;
670             if (! defined $templates{$filename}) {
671                if (&ReadTemplateFile ("$TMPL_DIR/$filename", 1)) {
672                    &MergeSourceDocumentation;
673                    $templates{$filename}=$.;
674                }
675             } else {
676                 &LogWarning ($file, $., "Double <FILE>$filename</FILE> entry. ".
677                     "Previous occurrence on line ".$templates{$filename}.".");
678             }
679             if (($title eq "") and (defined $SourceSymbolDocs{"$TMPL_DIR/$filename:Title"})) {
680                 $title = $SourceSymbolDocs{"$TMPL_DIR/$filename:Title"};
681                  # Remove trailing blanks
682                 $title =~ s/\s+$//;
683            }
685         } elsif (m/^<INCLUDE>(.*)<\/INCLUDE>/) {
686             if ($in_section) {
687                 $section_includes = $1;
688             } else {
689                 if (defined $DEFAULT_INCLUDES) {
690                     &LogWarning ($file, $., "Default <INCLUDE> being overridden by command line option.");
691                 }
692                 else {
693                     $includes = $1;
694                 }
695             }
697         } elsif (m/^<\/SECTION>/) {
698             @TRACE@("End of section: $title\n");
699             if ($num_symbols > 0) {
700                 # collect documents
701                 if ($OUTPUT_FORMAT eq "xml") {
702                     $book_bottom .= "    <xi:include href=\"xml/$filename.xml\"/>\n";
703                 } else {
704                     $book_top.="<!ENTITY $section_id SYSTEM \"sgml/$filename.sgml\">\n";
705                     $book_bottom .= "    &$section_id;\n";
706                 }
708                 if (defined ($SourceSymbolDocs{"$TMPL_DIR/$filename:Include"})) {
709                     if ($section_includes) {
710                         &LogWarning ($file, $., "Section <INCLUDE> being overridden by inline comments.");
711                     }
712                     $section_includes = $SourceSymbolDocs{"$TMPL_DIR/$filename:Include"};
713                 }
714                 if ($section_includes eq "") {
715                     $section_includes = $includes;
716                 }
718                  $signals_synop =~ s/^\n*//g;
719                  $signals_synop =~ s/\n+$/\n/g;
720                 if ($signals_synop ne '') {
721                     $signals_synop = <<EOF;
722 <refsect1 id="$section_id.signals" role="signal_proto">
723 <title role="signal_proto.title">Signals</title>
724 <informaltable frame="none">
725 <tgroup cols="3">
726 <colspec colname="signals_return" colwidth="150px"/>
727 <colspec colname="signals_name" colwidth="300px"/>
728 <colspec colname="signals_flags" colwidth="200px"/>
729 <tbody>
730 ${signals_synop}
731 </tbody>
732 </tgroup>
733 </informaltable>
734 </refsect1>
736                      $signals_desc = TrimTextBlock($signals_desc);
737                     $signals_desc  = <<EOF;
738 <refsect1 id="$section_id.signal-details" role="signals">
739 <title role="signals.title">Signal Details</title>
740 $signals_desc
741 </refsect1>
743                 }
745                 $args_synop =~ s/^\n*//g;
746                 $args_synop =~ s/\n+$/\n/g;
747                 if ($args_synop ne '') {
748                     $args_synop = <<EOF;
749 <refsect1 id="$section_id.properties" role="properties">
750 <title role="properties.title">Properties</title>
751 <informaltable frame="none">
752 <tgroup cols="3">
753 <colspec colname="properties_type" colwidth="150px"/>
754 <colspec colname="properties_name" colwidth="300px"/>
755 <colspec colname="properties_flags" colwidth="200px"/>
756 <tbody>
757 ${args_synop}
758 </tbody>
759 </tgroup>
760 </informaltable>
761 </refsect1>
763                      $args_desc = TrimTextBlock($args_desc);
764                     $args_desc  = <<EOF;
765 <refsect1 id="$section_id.property-details" role="property_details">
766 <title role="property_details.title">Property Details</title>
767 $args_desc
768 </refsect1>
770                 }
772                 $child_args_synop =~ s/^\n*//g;
773                 $child_args_synop =~ s/\n+$/\n/g;
774                 if ($child_args_synop ne '') {
775                     $args_synop .= <<EOF;
776 <refsect1 id="$section_id.child-properties" role="child_properties">
777 <title role="child_properties.title">Child Properties</title>
778 <informaltable frame="none">
779 <tgroup cols="3">
780 <colspec colname="child_properties_type" colwidth="150px"/>
781 <colspec colname="child_properties_name" colwidth="300px"/>
782 <colspec colname="child_properties_flags" colwidth="200px"/>
783 <tbody>
784 ${child_args_synop}
785 </tbody>
786 </tgroup>
787 </informaltable>
788 </refsect1>
790                      $child_args_desc = TrimTextBlock($child_args_desc);
791                      $args_desc .= <<EOF;
792 <refsect1 id="$section_id.child-property-details" role="child_property_details">
793 <title role="child_property_details.title">Child Property Details</title>
794 $child_args_desc
795 </refsect1>
797                 }
799                 $style_args_synop =~ s/^\n*//g;
800                 $style_args_synop =~ s/\n+$/\n/g;
801                 if ($style_args_synop ne '') {
802                     $args_synop .= <<EOF;
803 <refsect1 id="$section_id.style-properties" role="style_properties">
804 <title role="style_properties.title">Style Properties</title>
805 <informaltable frame="none">
806 <tgroup cols="3">
807 <colspec colname="style_properties_type" colwidth="150px"/>
808 <colspec colname="style_properties_name" colwidth="300px"/>
809 <colspec colname="style_properties_flags" colwidth="200px"/>
810 <tbody>
811 ${style_args_synop}
812 </tbody>
813 </tgroup>
814 </informaltable>
815 </refsect1>
817                      $style_args_desc = TrimTextBlock($style_args_desc);
818                     $args_desc .= <<EOF;
819 <refsect1 id="$section_id.style-property-details" role="style_properties_details">
820 <title role="style_properties_details.title">Style Property Details</title>
821 $style_args_desc
822 </refsect1>
824                 }
826                 $hierarchy_str = &AddTreeLineArt(\@hierarchy);
827                 if ($hierarchy_str ne "") {
828                     $hierarchy_str = <<EOF;
829 <refsect1 id="$section_id.object-hierarchy" role="object_hierarchy">
830 <title role="object_hierarchy.title">Object Hierarchy</title>
831 <screen>$hierarchy_str
832 </screen>
833 </refsect1>
835                 }
837                  $interfaces =~ TrimTextBlock($interfaces);
838                 if ($interfaces ne "") {
839                     $interfaces = <<EOF;
840 <refsect1 id="$section_id.implemented-interfaces" role="impl_interfaces">
841 <title role="impl_interfaces.title">Implemented Interfaces</title>
842 $interfaces
843 </refsect1>
845                 }
847                  $implementations = TrimTextBlock($implementations);
848                 if ($implementations ne "") {
849                     $implementations = <<EOF;
850 <refsect1 id="$section_id.implementations" role="implementations">
851 <title role="implementations.title">Known Implementations</title>
852 $implementations
853 </refsect1>
855                 }
857                  $prerequisites = TrimTextBlock($prerequisites);
858                 if ($prerequisites ne "") {
859                     $prerequisites = <<EOF;
860 <refsect1 id="$section_id.prerequisites" role="prerequisites">
861 <title role="prerequisites.title">Prerequisites</title>
862 $prerequisites
863 </refsect1>
865                 }
867                  $derived = TrimTextBlock($derived);
868                 if ($derived ne "") {
869                     $derived = <<EOF;
870 <refsect1 id="$section_id.derived-interfaces" role="derived_interfaces">
871 <title role="derived_interfaces.title">Known Derived Interfaces</title>
872 $derived
873 </refsect1>
875                 }
877                 $functions_synop =~ s/^\n*//g;
878                 $functions_synop =~ s/\n+$/\n/g;
879                 if ($functions_synop ne '') {
880                   $functions_synop = <<EOF;
881 <refsect1 id="$section_id.functions" role="functions_proto">
882 <title role="functions_proto.title">Functions</title>
883 <informaltable pgwide="1" frame="none">
884 <tgroup cols="2">
885 <colspec colname="functions_return" colwidth="150px"/>
886 <colspec colname="functions_name"/>
887 <tbody>
888 ${functions_synop}
889 </tbody>
890 </tgroup>
891 </informaltable>
892 </refsect1>
894                 }
896                 $other_synop =~ s/^\n*//g;
897                 $other_synop =~ s/\n+$/\n/g;
898                 if ($other_synop ne '') {
899                   $other_synop = <<EOF;
900 <refsect1 id="$section_id.other" role="other_proto">
901 <title role="other_proto.title">Types and Values</title>
902 <informaltable role="enum_members_table" pgwide="1" frame="none">
903 <tgroup cols="2">
904 <colspec colname="name" colwidth="150px"/>
905 <colspec colname="description"/>
906 <tbody>
907 ${other_synop}
908 </tbody>
909 </tgroup>
910 </informaltable>
911 </refsect1>
913                 }
915                 my $file_changed = &OutputSGMLFile ($filename, $title, $section_id,
916                                                     $section_includes,
917                                                     \$functions_synop, \$other_synop,
918                                                     \$functions_details, \$other_details,
919                                                     \$signals_synop, \$signals_desc,
920                                                     \$args_synop, \$args_desc,
921                                                     \$hierarchy_str, \$interfaces,
922                                                     \$implementations,
923                                                     \$prerequisites, \$derived,
924                                                     \@file_objects);
925                 if ($file_changed) {
926                     $changed = 1;
927                 }
928             }
929             $title = "";
930             $section_id = "";
931             $subsection = "";
932             $in_section = 0;
933             $section_includes = "";
934             $functions_synop = "";
935             $other_synop = "";
936             $functions_details = "";
937             $other_details = "";
938             $signals_synop = "";
939             $signals_desc = "";
940             $args_synop = "";
941             $child_args_synop = "";
942             $style_args_synop = "";
943             $args_desc = "";
944             $child_args_desc = "";
945             $style_args_desc = "";
946             $hierarchy_str = "";
947             @hierarchy = ();
948             $interfaces = "";
949             $implementations = "";
950             $prerequisites = "";
951             $derived = "";
953         } elsif (m/^(\S+)/) {
954             my $symbol = $1;
955             @TRACE@("  Symbol: $symbol in subsection: $subsection\n");
957             # check for duplicate entries
958             if (! defined $symbol_def_line{$symbol}) {
959                 my $declaration = $Declarations{$symbol};
960                 if (defined ($declaration)) {
961                     if (&CheckIsObject ($symbol)) {
962                         push @file_objects, $symbol;
963                     }
964                     # We don't want standard macros/functions of GObjects,
965                     # or private declarations.
966                     if ($subsection ne "Standard" && $subsection ne "Private") {
967                         my ($synop, $desc) = &OutputDeclaration ($symbol,
968                                                                  $declaration);
969                         my $type = $DeclarationTypes {$symbol};
970         
971                         if ($type eq 'FUNCTION' || $type eq 'USER_FUNCTION') {
972                           $functions_synop .= $synop;
973                           $functions_details .= $desc;
974                         } elsif ($type eq 'MACRO' && $declaration =~ /$symbol[ ]*\(/) {
975                           $functions_synop .= $synop;
976                           $functions_details .= $desc;
977                         } else {
978                           $other_synop .= $synop;
979                           $other_details .= $desc;
980                         }
981                     }
982                     my ($sig_synop, $sig_desc) = &GetSignals ($symbol);
983                     my ($arg_synop, $child_arg_synop, $style_arg_synop,
984                         $arg_desc, $child_arg_desc, $style_arg_desc) = &GetArgs ($symbol);
985                     my $ifaces = &GetInterfaces ($symbol);
986                     my $impls = &GetImplementations ($symbol);
987                     my $prereqs = &GetPrerequisites ($symbol);
988                     my $der = &GetDerived ($symbol);
989                     @hierarchy = &GetHierarchy ($symbol, \@hierarchy);
991                     $signals_synop .= $sig_synop;
992                     $signals_desc .= $sig_desc;
993                     $args_synop .= $arg_synop;
994                     $child_args_synop .= $child_arg_synop;
995                     $style_args_synop .= $style_arg_synop;
996                     $args_desc .= $arg_desc;
997                     $child_args_desc .= $child_arg_desc;
998                     $style_args_desc .= $style_arg_desc;
999                     $interfaces .= $ifaces;
1000                     $implementations .= $impls;
1001                     $prerequisites .= $prereqs;
1002                     $derived .= $der;
1004                     # Note that the declaration has been output.
1005                     $DeclarationOutput{$symbol} = 1;
1006                 } elsif ($subsection ne "Standard" && $subsection ne "Private") {
1007                     $UndeclaredSymbols{$symbol} = 1;
1008                     &LogWarning ($file, $., "No declaration found for $symbol.");
1009                 }
1010                 $num_symbols++;
1011                 $symbol_def_line{$symbol}=$.;
1013                 if ($section_id eq "") {
1014                     if($title eq "" && $filename eq "") {
1015                         &LogWarning ($file, $., "Section has no title and no file.");
1016                     }
1017                     # FIXME: one of those would be enough
1018                     # filename should be an internal detail for gtk-doc
1019                     if ($title eq "") {
1020                         $title = $filename;
1021                     } elsif ($filename eq "") {
1022                         $filename = $title;
1023                     }
1024                     $filename =~ s/\s/_/g;
1026                     $section_id = $SourceSymbolDocs{"$TMPL_DIR/$filename:Section_Id"};
1027                     if (defined ($section_id) && $section_id !~ m/^\s*$/) {
1028                         # Remove trailing blanks and use as is
1029                         $section_id =~ s/\s+$//;
1030                     } elsif (&CheckIsObject ($title)) {
1031                         # GObjects use their class name as the ID.
1032                         $section_id = &CreateValidSGMLID ($title);
1033                     } else {
1034                         $section_id = &CreateValidSGMLID ("$MODULE-$title");
1035                     }
1036                 }
1037                 $SymbolSection{$symbol}=$title;
1038                 $SymbolSectionId{$symbol}=$section_id;
1039             }
1040             else {
1041                 &LogWarning ($file, $., "Double symbol entry for $symbol. ".
1042                     "Previous occurrence on line ".$symbol_def_line{$symbol}.".");
1043             }
1044         }
1045     }
1046     close (INPUT);
1048     &OutputMissingDocumentation;
1049     &OutputUndeclaredSymbols;
1050     &OutputUnusedSymbols;
1052     if ($OUTPUT_ALL_SYMBOLS) {
1053         &OutputAllSymbols;
1054     }
1055     if ($OUTPUT_SYMBOLS_WITHOUT_SINCE) {
1056         &OutputSymbolsWithoutSince;
1057     }
1059     for $filename (split (' ', $EXPAND_CONTENT_FILES)) {
1060         my $file_changed = &OutputExtraFile ($filename);
1061         if ($file_changed) {
1062             $changed = 1;
1063         }
1064     }
1066     &OutputBook ($book_top, $book_bottom);
1068     return $changed;
1071 #############################################################################
1072 # Function    : OutputIndex
1073 # Description : This writes an indexlist that can be included into the main-
1074 #               document into an <index> tag.
1075 #############################################################################
1077 sub OutputIndex {
1078     my ($basename, $apiindexref ) = @_;
1079     my %apiindex = %{$apiindexref};
1080     my $old_index = "$SGML_OUTPUT_DIR/$basename.xml";
1081     my $new_index = "$SGML_OUTPUT_DIR/$basename.new";
1082     my $lastletter = " ";
1083     my $divopen = 0;
1084     my $symbol;
1085     my $short_symbol;
1087     open (OUTPUT, ">$new_index")
1088         || die "Can't create $new_index";
1090     my $header = $doctype_header;
1091     $header =~ s/<!DOCTYPE \w+/<!DOCTYPE indexdiv/;
1093     print (OUTPUT "$header<indexdiv>\n");
1095     @TRACE@("generate $basename index (".%apiindex." entries)\n");
1097     # do a case insensitive sort while chopping off the prefix
1098     foreach my $hash (
1099         sort { $$a{criteria} cmp $$b{criteria} or $$a{original} cmp $$b{original} }
1100         map { my $x = uc($_); $x =~ s/^$NAME_SPACE\_?(.*)/$1/i; { criteria => $x, original => $_, short => $1 } }
1101         keys %apiindex) {
1103         $symbol = $$hash{original};
1104         if (defined($$hash{short})) {
1105             $short_symbol = $$hash{short};
1106         } else {
1107             $short_symbol = $symbol;
1108         }
1110         # generate a short symbol description
1111         my $symbol_desc = "";
1112         my $symbol_section = "";
1113         my $symbol_section_id = "";
1114         my $symbol_type = "";
1115         if (defined($DeclarationTypes{$symbol})) {
1116           $symbol_type = lc($DeclarationTypes{$symbol});
1117         }
1118         if ($symbol_type eq "") {
1119             @TRACE@("trying symbol $symbol\n");
1120             if ($symbol =~ m/(.*)::(.*)/) {
1121                 my $oname = $1;
1122                 my $osym = $2;
1123                 my $i;
1124                 @TRACE@("  trying object signal ${oname}:$osym in ".$#SignalNames." signals\n");
1125                 for ($i = 0; $i <= $#SignalNames; $i++) {
1126                     if ($SignalNames[$i] eq $osym) {
1127                         $symbol_type = "object signal";
1128                         if (defined($SymbolSection{$oname})) {
1129                            $symbol_section = $SymbolSection{$oname};
1130                            $symbol_section_id = $SymbolSectionId{$oname};
1131                         }
1132                         last;
1133                     }
1134                 }
1135             } elsif ($symbol =~ m/(.*):(.*)/) {
1136                 my $oname = $1;
1137                 my $osym = $2;
1138                 my $i;
1139                 @TRACE@("  trying object property ${oname}::$osym in ".$#ArgNames." properties\n");
1140                 for ($i = 0; $i <= $#ArgNames; $i++) {
1141                     @TRACE@("    ".$ArgNames[$i]."\n");
1142                     if ($ArgNames[$i] eq $osym) {
1143                         $symbol_type = "object property";
1144                         if (defined($SymbolSection{$oname})) {
1145                            $symbol_section = $SymbolSection{$oname};
1146                            $symbol_section_id = $SymbolSectionId{$oname};
1147                         }
1148                         last;
1149                     }
1150                 }
1151             }
1152         } else {
1153            if (defined($SymbolSection{$symbol})) {
1154                $symbol_section = $SymbolSection{$symbol};
1155                $symbol_section_id = $SymbolSectionId{$symbol};
1156            }
1157         }
1158         if ($symbol_type ne "") {
1159            $symbol_desc=", $symbol_type";
1160            if ($symbol_section ne "") {
1161                $symbol_desc.=" in <link linkend=\"$symbol_section_id\">$symbol_section</link>";
1162                #$symbol_desc.=" in ". &ExpandAbbreviations($symbol, "#$symbol_section");
1163            }
1164         }
1166         my $curletter = uc(substr($short_symbol,0,1));
1167         my $id = $apiindex{$symbol};
1169         @TRACE@("  add symbol $symbol with $id to index in section $curletter\n");
1171         if ($curletter ne $lastletter) {
1172             $lastletter = $curletter;
1174             if ($divopen == 1) {
1175                 print (OUTPUT "</indexdiv>\n");
1176             }
1177             print (OUTPUT "<indexdiv><title>$curletter</title>\n");
1178             $divopen = 1;
1179         }
1181         print (OUTPUT <<EOF);
1182 <indexentry><primaryie linkends="$id"><link linkend="$id">$symbol</link>$symbol_desc</primaryie></indexentry>
1184     }
1186     if ($divopen == 1) {
1187         print (OUTPUT "</indexdiv>\n");
1188     }
1189     print (OUTPUT "</indexdiv>\n");
1190     close (OUTPUT);
1192     &UpdateFileIfChanged ($old_index, $new_index, 0);
1196 #############################################################################
1197 # Function    : OutputIndexFull
1198 # Description : This writes the full api indexlist that can be included into the
1199 #               main document into an <index> tag.
1200 #############################################################################
1202 sub OutputIndexFull {
1203     &OutputIndex ("api-index-full", \%IndexEntriesFull);
1207 #############################################################################
1208 # Function    : OutputDeprecatedIndex
1209 # Description : This writes the deprecated api indexlist that can be included
1210 #               into the main document into an <index> tag.
1211 #############################################################################
1213 sub OutputDeprecatedIndex {
1214     &OutputIndex ("api-index-deprecated", \%IndexEntriesDeprecated);
1218 #############################################################################
1219 # Function    : OutputSinceIndexes
1220 # Description : This writes the 'since' api indexlists that can be included into
1221 #               the main document into an <index> tag.
1222 #############################################################################
1224 sub OutputSinceIndexes {
1225     my @sinces = keys %{{ map { $_ => 1 } values %Since }};
1227     foreach my $version (@sinces) {
1228         @TRACE@("Since : [$version]\n");
1229         # TODO make filtered hash
1230         #my %index = grep { $Since{$_} eq $version } %IndexEntriesSince;
1231         my %index = map { $_ => $IndexEntriesSince{$_} } grep { $Since{$_} eq $version } keys %IndexEntriesSince;
1233         &OutputIndex ("api-index-$version", \%index);
1234     }
1237 #############################################################################
1238 # Function    : OutputAnnotationGlossary
1239 # Description : This writes a glossary of the used annotation terms into a
1240 #               separate glossary file that can be included into the main
1241 #               document.
1242 #############################################################################
1244 sub OutputAnnotationGlossary {
1245     my $old_glossary = "$SGML_OUTPUT_DIR/annotation-glossary.xml";
1246     my $new_glossary = "$SGML_OUTPUT_DIR/annotation-glossary.new";
1247     my $lastletter = " ";
1248     my $divopen = 0;
1250     # if there are no annotations used return
1251     return if (! keys(%AnnotationsUsed));
1253     # add acronyms that are referenced from acronym text
1254 rerun:
1255     foreach my $annotation (keys(%AnnotationsUsed)) {
1256         if(defined($AnnotationDefinition{$annotation})) {
1257             if($AnnotationDefinition{$annotation} =~ m/<acronym>([\w ]+)<\/acronym>/) {
1258                 if (!exists($AnnotationsUsed{$1})) {
1259                     $AnnotationsUsed{$1} = 1;
1260                     goto rerun;
1261                 }
1262             }
1263         }
1264     }
1266     open (OUTPUT, ">$new_glossary")
1267         || die "Can't create $new_glossary";
1269     my $header = $doctype_header;
1270     $header =~ s/<!DOCTYPE \w+/<!DOCTYPE glossary/;
1272     print (OUTPUT  <<EOF);
1273 $header
1274 <glossary id="annotation-glossary">
1275   <title>Annotation Glossary</title>
1278     foreach my $annotation (sort({lc $a cmp lc $b} keys(%AnnotationsUsed))) {
1279         if(defined($AnnotationDefinition{$annotation})) {
1280             my $def = $AnnotationDefinition{$annotation};
1281             my $curletter = uc(substr($annotation,0,1));
1283             if ($curletter ne $lastletter) {
1284                 $lastletter = $curletter;
1286                 if ($divopen == 1) {
1287                     print (OUTPUT "</glossdiv>\n");
1288                 }
1289                 print (OUTPUT "<glossdiv><title>$curletter</title>\n");
1290                 $divopen = 1;
1291             }
1292             print (OUTPUT <<EOF);
1293     <glossentry>
1294       <glossterm><anchor id="annotation-glossterm-$annotation"/>$annotation</glossterm>
1295       <glossdef>
1296         <para>$def</para>
1297       </glossdef>
1298     </glossentry>
1300         }
1301     }
1303     if ($divopen == 1) {
1304         print (OUTPUT "</glossdiv>\n");
1305     }
1306     print (OUTPUT "</glossary>\n");
1307     close (OUTPUT);
1309     &UpdateFileIfChanged ($old_glossary, $new_glossary, 0);
1312 #############################################################################
1313 # Function    : ReadKnownSymbols
1314 # Description : This collects the names of non-private symbols from the
1315 #               $MODULE-sections.txt file.
1316 # Arguments   : $file - the $MODULE-sections.txt file which contains all of
1317 #                the functions/macros/structs etc. being documented, organised
1318 #                into sections and subsections.
1319 #############################################################################
1321 sub ReadKnownSymbols {
1322     my ($file) = @_;
1324     my $subsection = "";
1326     @TRACE@("Reading: $file\n");
1327     open (INPUT, $file)
1328         || die "Can't open $file: $!";
1330     while (<INPUT>) {
1331         if (m/^#/) {
1332             next;
1334         } elsif (m/^<SECTION>/) {
1335             $subsection = "";
1337         } elsif (m/^<SUBSECTION\s*(.*)>/i) {
1338             $subsection = $1;
1340         } elsif (m/^<SUBSECTION>/) {
1341             next;
1343         } elsif (m/^<TITLE>(.*)<\/TITLE>/) {
1344             next;
1346         } elsif (m/^<FILE>(.*)<\/FILE>/) {
1347             $KnownSymbols{"$TMPL_DIR/$1:Long_Description"} = 1;
1348             $KnownSymbols{"$TMPL_DIR/$1:Short_Description"} = 1;
1349             next;
1351         } elsif (m/^<INCLUDE>(.*)<\/INCLUDE>/) {
1352             next;
1354         } elsif (m/^<\/SECTION>/) {
1355             next;
1357         } elsif (m/^(\S+)/) {
1358             my $symbol = $1;
1360             if ($subsection ne "Standard" && $subsection ne "Private") {
1361                 $KnownSymbols{$symbol} = 1;
1362             }
1363             else {
1364                 $KnownSymbols{$symbol} = 0;
1365             }
1366         }
1367     }
1368     close (INPUT);
1372 #############################################################################
1373 # Function    : OutputDeclaration
1374 # Description : Returns the synopsis and detailed description DocBook
1375 #                describing one function/macro etc.
1376 # Arguments   : $symbol - the name of the function/macro begin described.
1377 #                $declaration - the declaration of the function/macro.
1378 #############################################################################
1380 sub OutputDeclaration {
1381     my ($symbol, $declaration) = @_;
1383     my $type = $DeclarationTypes {$symbol};
1384     if ($type eq 'MACRO') {
1385         return &OutputMacro ($symbol, $declaration);
1386     } elsif ($type eq 'TYPEDEF') {
1387         return &OutputTypedef ($symbol, $declaration);
1388     } elsif ($type eq 'STRUCT') {
1389         return &OutputStruct ($symbol, $declaration);
1390     } elsif ($type eq 'ENUM') {
1391         return &OutputEnum ($symbol, $declaration);
1392     } elsif ($type eq 'UNION') {
1393         return &OutputUnion ($symbol, $declaration);
1394     } elsif ($type eq 'VARIABLE') {
1395         return &OutputVariable ($symbol, $declaration);
1396     } elsif ($type eq 'FUNCTION') {
1397         return &OutputFunction ($symbol, $declaration, $type);
1398     } elsif ($type eq 'USER_FUNCTION') {
1399         return &OutputFunction ($symbol, $declaration, $type);
1400     } else {
1401         die "Unknown symbol type";
1402     }
1406 #############################################################################
1407 # Function    : OutputSymbolTraits
1408 # Description : Returns the Since and StabilityLevel paragraphs for a symbol.
1409 # Arguments   : $symbol - the name of the function/macro begin described.
1410 #############################################################################
1412 sub OutputSymbolTraits {
1413     my ($symbol) = @_;
1414     my $desc = "";
1416     if (exists $Since{$symbol}) {
1417         $desc .= "<para role=\"since\">Since $Since{$symbol}</para>";
1418     }
1419     if (exists $StabilityLevel{$symbol}) {
1420         my $stability = $StabilityLevel{$symbol};
1421         $AnnotationsUsed{$stability} = 1;
1422         $desc .= "<para role=\"stability\">Stability Level: <acronym>$stability</acronym></para>";
1423     }
1424     return $desc;
1427 #############################################################################
1428 # Function    : Output{Symbol,Section}ExtraLinks
1429 # Description : Returns extralinks for the symbol (if enabled).
1430 # Arguments   : $symbol - the name of the function/macro begin described.
1431 #############################################################################
1433 sub uri_escape {
1434     my $text = $_[0];
1435     return undef unless defined $text;
1437     # Build a char to hex map
1438     my %escapes = ();
1439     for (0..255) {
1440             $escapes{chr($_)} = sprintf("%%%02X", $_);
1441     }
1443     # Default unsafe characters.  RFC 2732 ^(uric - reserved)
1444     $text =~ s/([^A-Za-z0-9\-_.!~*'()])/$escapes{$1}/g;
1446     return $text;
1449 sub OutputSymbolExtraLinks {
1450     my ($symbol) = @_;
1451     my $desc = "";
1453     if (0) { # NEW FEATURE: needs configurability
1454     my $sstr = &uri_escape($symbol);
1455     my $mstr = &uri_escape($MODULE);
1456     $desc .= <<EOF;
1457 <ulink role="extralinks" url="http://www.google.com/codesearch?q=$sstr">code search</ulink>
1458 <ulink role="extralinks" url="http://library.gnome.org/edit?module=$mstr&amp;symbol=$sstr">edit documentation</ulink>
1460     }
1461     return $desc;
1464 sub OutputSectionExtraLinks {
1465     my ($symbol,$docsymbol) = @_;
1466     my $desc = "";
1468     if (0) { # NEW FEATURE: needs configurability
1469     my $sstr = &uri_escape($symbol);
1470     my $mstr = &uri_escape($MODULE);
1471     my $dsstr = &uri_escape($docsymbol);
1472     $desc .= <<EOF;
1473 <ulink role="extralinks" url="http://www.google.com/codesearch?q=$sstr">code search</ulink>
1474 <ulink role="extralinks" url="http://library.gnome.org/edit?module=$mstr&amp;symbol=$dsstr">edit documentation</ulink>
1476     }
1477     return $desc;
1481 #############################################################################
1482 # Function    : OutputMacro
1483 # Description : Returns the synopsis and detailed description of a macro.
1484 # Arguments   : $symbol - the macro.
1485 #                $declaration - the declaration of the macro.
1486 #############################################################################
1488 sub OutputMacro {
1489     my ($symbol, $declaration) = @_;
1490     my $id = &CreateValidSGMLID ($symbol);
1491     my $condition = &MakeConditionDescription ($symbol);
1492     my $synop = "<row><entry role=\"define_keyword\">#define</entry><entry role=\"function_name\"><link linkend=\"$id\">$symbol</link>";
1493     my $desc;
1495     my @fields = ParseMacroDeclaration($declaration, \&CreateValidSGML);
1496     my $title = $symbol . (@fields ? "()" : "");
1498     $desc = "<refsect2 id=\"$id\" role=\"macro\"$condition>\n<title>$title</title>\n";
1499     $desc .= MakeIndexterms($symbol, $id);
1500     $desc .= "\n";
1501     $desc .= OutputSymbolExtraLinks($symbol);
1503     if (@fields) {
1504         $synop .= "<phrase role=\"c_punctuation\">()</phrase>";
1505     }
1506     $synop .= "</entry></row>\n";
1508     # Don't output the macro definition if is is a conditional macro or it
1509     # looks like a function, i.e. starts with "g_" or "_?gnome_", or it is
1510     # longer than 2 lines, otherwise we get lots of complicated macros like
1511     # g_assert.
1512     if (!defined ($DeclarationConditional{$symbol}) && ($symbol !~ m/^g_/)
1513         && ($symbol !~ m/^_?gnome_/) && (($declaration =~ tr/\n//) < 2)) {
1514         my $decl_out = &CreateValidSGML ($declaration);
1515         $desc .= "<programlisting language=\"C\">$decl_out</programlisting>\n";
1516     } else {
1517         $desc .= "<programlisting language=\"C\">" . &MakeReturnField("#define") . "$symbol";
1518         if ($declaration =~ m/^\s*#\s*define\s+\w+(\([^\)]*\))/) {
1519             my $args = $1;
1520             my $pad = ' ' x ($RETURN_TYPE_FIELD_WIDTH - length ("#define "));
1521             # Align each line so that if should all line up OK.
1522             $args =~ s/\n/\n$pad/gm;
1523             $desc .= &CreateValidSGML ($args);
1524         }
1525         $desc .= "</programlisting>\n";
1526     }
1528     $desc .= &MakeDeprecationNote($symbol);
1530     my $parameters = &OutputParamDescriptions ("MACRO", $symbol, @fields);
1532     if (defined ($SymbolDocs{$symbol})) {
1533         my $symbol_docs = &ConvertMarkDown($symbol, $SymbolDocs{$symbol});
1534         $desc .= $symbol_docs;
1535     }
1537     $desc .= $parameters;
1538     $desc .= OutputSymbolTraits ($symbol);
1539     $desc .= "</refsect2>\n";
1540     return ($synop, $desc);
1544 #############################################################################
1545 # Function    : OutputTypedef
1546 # Description : Returns the synopsis and detailed description of a typedef.
1547 # Arguments   : $symbol - the typedef.
1548 #                $declaration - the declaration of the typedef,
1549 #                  e.g. 'typedef unsigned int guint;'
1550 #############################################################################
1552 sub OutputTypedef {
1553     my ($symbol, $declaration) = @_;
1554     my $id = &CreateValidSGMLID ($symbol);
1555     my $condition = &MakeConditionDescription ($symbol);
1556     my $desc = "<refsect2 id=\"$id\" role=\"typedef\"$condition>\n<title>$symbol</title>\n";
1557     my $synop = "<row><entry role=\"typedef_keyword\">typedef</entry><entry role=\"function_name\"><link linkend=\"$id\">$symbol</link></entry></row>\n";
1559     $desc .= MakeIndexterms($symbol, $id);
1560     $desc .= "\n";
1561     $desc .= OutputSymbolExtraLinks($symbol);
1563     if (!defined ($DeclarationConditional{$symbol})) {
1564         my $decl_out = &CreateValidSGML ($declaration);
1565         $desc .= "<programlisting language=\"C\">$decl_out</programlisting>\n";
1566     }
1568     $desc .= &MakeDeprecationNote($symbol);
1570     if (defined ($SymbolDocs{$symbol})) {
1571         $desc .= &ConvertMarkDown($symbol, $SymbolDocs{$symbol});
1572     }
1573     $desc .= OutputSymbolTraits ($symbol);
1574     $desc .= "</refsect2>\n";
1575     return ($synop, $desc);
1579 #############################################################################
1580 # Function    : OutputStruct
1581 # Description : Returns the synopsis and detailed description of a struct.
1582 #                We check if it is a object struct, and if so we only output
1583 #                parts of it that are noted as public fields.
1584 #                We also use a different SGML ID for object structs, since the
1585 #                original ID is used for the entire RefEntry.
1586 # Arguments   : $symbol - the struct.
1587 #                $declaration - the declaration of the struct.
1588 #############################################################################
1590 sub OutputStruct {
1591     my ($symbol, $declaration) = @_;
1593     my $is_gtype = 0;
1594     my $default_to_public = 1;
1595     if (&CheckIsObject ($symbol)) {
1596         @TRACE@("Found struct gtype: $symbol\n");
1597         $is_gtype = 1;
1598         $default_to_public = $ObjectRoots{$symbol} eq 'GBoxed';
1599     }
1601     my $id;
1602     my $condition;
1603     if ($is_gtype) {
1604         $id = &CreateValidSGMLID ($symbol . "_struct");
1605         $condition = &MakeConditionDescription ($symbol . "_struct");
1606     } else {
1607         $id = &CreateValidSGMLID ($symbol);
1608         $condition = &MakeConditionDescription ($symbol);
1609     }
1611     # Determine if it is a simple struct or it also has a typedef.
1612     my $has_typedef = 0;
1613     if ($StructHasTypedef{$symbol} || $declaration =~ m/^\s*typedef\s+/) {
1614       $has_typedef = 1;
1615     }
1617     my $type_output;
1618     my $desc;
1619     if ($has_typedef) {
1620         # For structs with typedefs we just output the struct name.
1621         $type_output = "";
1622         $desc = "<refsect2 id=\"$id\" role=\"struct\"$condition>\n<title>$symbol</title>\n";
1623     } else {
1624         $type_output = "struct";
1625         $desc = "<refsect2 id=\"$id\" role=\"struct\"$condition>\n<title>struct $symbol</title>\n";
1626     }
1627     my $synop = "<row><entry role=\"datatype_keyword\">${type_output}</entry><entry role=\"function_name\"><link linkend=\"$id\">$symbol</link></entry></row>\n";
1629     $desc .= MakeIndexterms($symbol, $id);
1630     $desc .= "\n";
1631     $desc .= OutputSymbolExtraLinks($symbol);
1633     # Form a pretty-printed, private-data-removed form of the declaration
1635     my $decl_out = "";
1636     if ($declaration =~ m/^\s*$/) {
1637         @TRACE@("Found opaque struct: $symbol\n");
1638         $decl_out = "typedef struct _$symbol $symbol;";
1639     } elsif ($declaration =~ m/^\s*struct\s+\w+\s*;\s*$/) {
1640         @TRACE@("Found opaque struct: $symbol\n");
1641         $decl_out = "struct $symbol;";
1642     } else {
1643         my $public = $default_to_public;
1644         my $new_declaration = "";
1645         my $decl_line;
1646         my $decl = $declaration;
1648         if ($decl =~ m/^\s*(typedef\s+)?struct\s*\w*\s*(?:\/\*.*\*\/)?\s*{(.*)}\s*\w*\s*;\s*$/s) {
1649             my $struct_contents = $2;
1651             foreach $decl_line (split (/\n/, $struct_contents)) {
1652                 @TRACE@("Struct line: $decl_line\n");
1653                 if ($decl_line =~ m%/\*\s*<\s*public\s*>\s*\*/%) {
1654                     $public = 1;
1655                 } elsif ($decl_line =~ m%/\*\s*<\s*(private|protected)\s*>\s*\*/%) {
1656                     $public = 0;
1657                 } elsif ($public) {
1658                     $new_declaration .= $decl_line . "\n";
1659                 }
1660             }
1662             if ($new_declaration) {
1663                 # Strip any blank lines off the ends.
1664                 $new_declaration =~ s/^\s*\n//;
1665                 $new_declaration =~ s/\n\s*$/\n/;
1667                 if ($has_typedef) {
1668                     $decl_out = "typedef struct {\n" . $new_declaration
1669                       . "} $symbol;\n";
1670                 } else {
1671                     $decl_out = "struct $symbol {\n" . $new_declaration
1672                       . "};\n";
1673                 }
1674             }
1675         } else {
1676             &LogWarning (&GetSymbolSourceFile ($symbol), &GetSymbolSourceLine($symbol),
1677                 "Couldn't parse struct:\n$declaration");
1678         }
1680         # If we couldn't parse the struct or it was all private, output an
1681         # empty struct declaration.
1682         if ($decl_out eq "") {
1683             if ($has_typedef) {
1684                 $decl_out = "typedef struct _$symbol $symbol;";
1685             } else {
1686                 $decl_out = "struct $symbol;";
1687             }
1688         }
1689     }
1691     $decl_out = &CreateValidSGML ($decl_out);
1692     $desc .= "<programlisting language=\"C\">$decl_out</programlisting>\n";
1694     $desc .= &MakeDeprecationNote($symbol);
1696     if (defined ($SymbolDocs{$symbol})) {
1697         $desc .= &ConvertMarkDown($symbol, $SymbolDocs{$symbol});
1698     }
1700     # Create a table of fields and descriptions
1702     # FIXME: Inserting &#160's into the produced type declarations here would
1703     #        improve the output in most situations ... except for function
1704     #        members of structs!
1705     my @fields = ParseStructDeclaration($declaration, !$default_to_public,
1706                                         0, \&MakeXRef,
1707                                         sub {
1708                                             "<structfield id=\"".&CreateValidSGMLID("$id.$_[0]")."\">$_[0]</structfield>";
1709                                         });
1710     my $params = $SymbolParams{$symbol};
1712     # If no parameters are filled in, we don't generate the description
1713     # table, for backwards compatibility.
1715     my $found = 0;
1716     if (defined $params) {
1717         for (my $i = 1; $i <= $#$params; $i += $PARAM_FIELD_COUNT) {
1718             if ($params->[$i] =~ /\S/) {
1719                 $found = 1;
1720                 last;
1721             }
1722         }
1723     }
1725     if ($found) {
1726         my %field_descrs = @$params;
1727         my $missing_parameters = "";
1728         my $unused_parameters = "";
1730         $desc .= <<EOF;
1731 <refsect3 role="struct_members">\n<title>Members</title>
1732 <informaltable role="struct_members_table" pgwide="1" frame="none">
1733 <tgroup cols="3">
1734 <colspec colname="struct_members_name" colwidth="300px"/>
1735 <colspec colname="struct_members_description"/>
1736 <colspec colname="struct_members_annotations" colwidth="200px"/>
1737 <tbody>
1740         while (@fields) {
1741             my $field_name = shift @fields;
1742             my $text = shift @fields;
1743             my $field_descr = $field_descrs{$field_name};
1744             my $param_annotations = "";
1746             $desc .= "<row><entry role=\"struct_member_name\"><para>$text</para></entry>\n";
1747             if (defined $field_descr) {
1748                 ($field_descr,$param_annotations) = &ExpandAnnotation($symbol, $field_descr);
1749                 $field_descr = &ConvertMarkDown($symbol, $field_descr);
1750                 # trim
1751                 $field_descr =~ s/^(\s|\n)+//msg;
1752                 $field_descr =~ s/(\s|\n)+$//msg;
1753                 $desc .= "<listitem>$field_descr</listitem>\n";
1754                 $desc .= "<entry role=\"struct_member_description\">$field_descr</entry>\n<entry role=\"struct_member_annotations\">$param_annotations</entry>\n";
1755                 delete $field_descrs{$field_name};
1756             } else {
1757                 &LogWarning (&GetSymbolSourceFile ($symbol), &GetSymbolSourceLine($symbol),
1758                     "Field description for $symbol"."::"."$field_name is missing in source code comment block.");
1759                 if ($missing_parameters ne "") {
1760                   $missing_parameters .= ", ".$field_name;
1761                 } else {
1762                     $missing_parameters = $field_name;
1763                 }
1764                 $desc .= "<entry /><entry />\n";
1765             }
1766             $desc .= "</row>\n";
1767         }
1768         $desc .= "</tbody></tgroup></informaltable>\n</refsect3>\n";
1769         foreach my $field_name (keys %field_descrs) {
1770             &LogWarning (&GetSymbolSourceFile ($symbol), &GetSymbolSourceLine($symbol),
1771                 "Field description for $symbol"."::"."$field_name is not used from source code comment block.");
1772             if ($unused_parameters ne "") {
1773               $unused_parameters .= ", ".$field_name;
1774             } else {
1775                $unused_parameters = $field_name;
1776             }
1777         }
1779         # remember missing/unused parameters (needed in tmpl-free build)
1780         if (($missing_parameters ne "") and (! exists ($AllIncompleteSymbols{$symbol}))) {
1781             $AllIncompleteSymbols{$symbol}=$missing_parameters;
1782         }
1783         if (($unused_parameters ne "") and (! exists ($AllUnusedSymbols{$symbol}))) {
1784             $AllUnusedSymbols{$symbol}=$unused_parameters;
1785         }
1786     }
1787     else {
1788         if (scalar(@fields) > 0) {
1789             if (! exists ($AllIncompleteSymbols{$symbol})) {
1790                 $AllIncompleteSymbols{$symbol}="<items>";
1791                 &LogWarning (&GetSymbolSourceFile ($symbol), &GetSymbolSourceLine($symbol),
1792                     "Field descriptions for struct $symbol are missing in source code comment block.");
1793                 @TRACE@("Remaining structs fields: ".@fields.":".join(',',@fields)."\n");
1794             }
1795         }
1796     }
1798     $desc .= OutputSymbolTraits ($symbol);
1799     $desc .= "</refsect2>\n";
1800     return ($synop, $desc);
1804 #############################################################################
1805 # Function    : OutputUnion
1806 # Description : Returns the synopsis and detailed description of a union.
1807 # Arguments   : $symbol - the union.
1808 #                $declaration - the declaration of the union.
1809 #############################################################################
1811 sub OutputUnion {
1812     my ($symbol, $declaration) = @_;
1814     my $is_gtype = 0;
1815     if (&CheckIsObject ($symbol)) {
1816         @TRACE@("Found union gtype: $symbol\n");
1817         $is_gtype = 1;
1818     }
1820     my $id;
1821     my $condition;
1822     if ($is_gtype) {
1823         $id = &CreateValidSGMLID ($symbol . "_union");
1824         $condition = &MakeConditionDescription ($symbol . "_union");
1825     } else {
1826         $id = &CreateValidSGMLID ($symbol);
1827         $condition = &MakeConditionDescription ($symbol);
1828     }
1830     # Determine if it is a simple struct or it also has a typedef.
1831     my $has_typedef = 0;
1832     if ($StructHasTypedef{$symbol} || $declaration =~ m/^\s*typedef\s+/) {
1833       $has_typedef = 1;
1834     }
1836     my $type_output;
1837     my $desc;
1838     if ($has_typedef) {
1839         # For unions with typedefs we just output the union name.
1840         $type_output = "";
1841         $desc = "<refsect2 id=\"$id\" role=\"union\"$condition>\n<title>$symbol</title>\n";
1842     } else {
1843         $type_output = "union";
1844         $desc = "<refsect2 id=\"$id\" role=\"union\"$condition>\n<title>union $symbol</title>\n";
1845     }
1846     my $synop = "<row><entry role=\"datatype_keyword\">${type_output}</entry><entry role=\"function_name\"><link linkend=\"$id\">$symbol</link></entry></row>\n";
1848     $desc .= MakeIndexterms($symbol, $id);
1849     $desc .= "\n";
1850     $desc .= OutputSymbolExtraLinks($symbol);
1851     $desc .= &MakeDeprecationNote($symbol);
1853     if (defined ($SymbolDocs{$symbol})) {
1854         $desc .= &ConvertMarkDown($symbol, $SymbolDocs{$symbol});
1855     }
1857     # Create a table of fields and descriptions
1859     # FIXME: Inserting &#160's into the produced type declarations here would
1860     #        improve the output in most situations ... except for function
1861     #        members of structs!
1862     my @fields = ParseStructDeclaration($declaration, 0,
1863                                         0, \&MakeXRef,
1864                                         sub {
1865                                             "<structfield id=\"".&CreateValidSGMLID("$id.$_[0]")."\">$_[0]</structfield>";
1866                                         });
1867     my $params = $SymbolParams{$symbol};
1869     # If no parameters are filled in, we don't generate the description
1870     # table, for backwards compatibility
1872     my $found = 0;
1873     if (defined $params) {
1874         for (my $i = 1; $i <= $#$params; $i += $PARAM_FIELD_COUNT) {
1875             if ($params->[$i] =~ /\S/) {
1876                 $found = 1;
1877                 last;
1878             }
1879         }
1880     }
1882     if ($found) {
1883         my %field_descrs = @$params;
1884         my $missing_parameters = "";
1885         my $unused_parameters = "";
1887         $desc .= <<EOF;
1888 <refsect3 role="union_members">\n<title>Members</title>
1889 <informaltable role="union_members_table" pgwide="1" frame="none">
1890 <tgroup cols="3">
1891 <colspec colname="union_members_name" colwidth="300px"/>
1892 <colspec colname="union_members_description"/>
1893 <colspec colname="union_members_annotations" colwidth="200px"/>
1894 <tbody>
1897         while (@fields) {
1898             my $field_name = shift @fields;
1899             my $text = shift @fields;
1900             my $field_descr = $field_descrs{$field_name};
1901             my $param_annotations = "";
1903             $desc .= "<row><entry role=\"union_member_name\"><para>$text</para></entry>\n";
1904             if (defined $field_descr) {
1905                 ($field_descr,$param_annotations) = &ExpandAnnotation($symbol, $field_descr);
1906                 $field_descr = &ConvertMarkDown($symbol, $field_descr);
1908                 # trim
1909                 $field_descr =~ s/^(\s|\n)+//msg;
1910                 $field_descr =~ s/(\s|\n)+$//msg;
1911                 $desc .= "<entry role=\"union_member_description\">$field_descr</entry>\n<entry role=\"union_member_annotations\">$param_annotations</entry>\n";
1912                 delete $field_descrs{$field_name};
1913             } else {
1914                 &LogWarning (&GetSymbolSourceFile ($symbol), &GetSymbolSourceLine($symbol),
1915                     "Field description for $symbol"."::"."$field_name is missing in source code comment block.");
1916                 if ($missing_parameters ne "") {
1917                     $missing_parameters .= ", ".$field_name;
1918                 } else {
1919                     $missing_parameters = $field_name;
1920                 }
1921                 $desc .= "<entry /><entry />\n";
1922             }
1923             $desc .= "</row>\n";
1924         }
1925         $desc .= "</tbody></tgroup></informaltable>\n</refsect3>";
1926         foreach my $field_name (keys %field_descrs) {
1927             &LogWarning (&GetSymbolSourceFile ($symbol), &GetSymbolSourceLine($symbol),
1928                 "Field description for $symbol"."::"."$field_name is not used from source code comment block.");
1929             if ($unused_parameters ne "") {
1930               $unused_parameters .= ", ".$field_name;
1931             } else {
1932                $unused_parameters = $field_name;
1933             }
1934         }
1936         # remember missing/unused parameters (needed in tmpl-free build)
1937         if (($missing_parameters ne "") and (! exists ($AllIncompleteSymbols{$symbol}))) {
1938             $AllIncompleteSymbols{$symbol}=$missing_parameters;
1939         }
1940         if (($unused_parameters ne "") and (! exists ($AllUnusedSymbols{$symbol}))) {
1941             $AllUnusedSymbols{$symbol}=$unused_parameters;
1942         }
1943     }
1944     else {
1945         if (scalar(@fields) > 0) {
1946             if (! exists ($AllIncompleteSymbols{$symbol})) {
1947                 $AllIncompleteSymbols{$symbol}="<items>";
1948                 &LogWarning (&GetSymbolSourceFile ($symbol), &GetSymbolSourceLine($symbol),
1949                     "Field descriptions for union $symbol are missing in source code comment block.");
1950                 @TRACE@("Remaining union fields: ".@fields.":".join(',',@fields)."\n");
1951             }
1952         }
1953     }
1955     $desc .= OutputSymbolTraits ($symbol);
1956     $desc .= "</refsect2>\n";
1957     return ($synop, $desc);
1961 #############################################################################
1962 # Function    : OutputEnum
1963 # Description : Returns the synopsis and detailed description of a enum.
1964 # Arguments   : $symbol - the enum.
1965 #                $declaration - the declaration of the enum.
1966 #############################################################################
1968 sub OutputEnum {
1969     my ($symbol, $declaration) = @_;
1971     my $is_gtype = 0;
1972     if (&CheckIsObject ($symbol)) {
1973         @TRACE@("Found enum gtype: $symbol\n");
1974         $is_gtype = 1;
1975     }
1977     my $id;
1978     my $condition;
1979     if ($is_gtype) {
1980         $id = &CreateValidSGMLID ($symbol . "_enum");
1981         $condition = &MakeConditionDescription ($symbol . "_enum");
1982     } else {
1983         $id = &CreateValidSGMLID ($symbol);
1984         $condition = &MakeConditionDescription ($symbol);
1985     }
1987     my $synop = "<row><entry role=\"datatype_keyword\">enum</entry><entry role=\"function_name\"><link linkend=\"$id\">$symbol</link></entry></row>\n";
1988     my $desc = "<refsect2 id=\"$id\" role=\"enum\"$condition>\n<title>enum $symbol</title>\n";
1990     $desc .= MakeIndexterms($symbol, $id);
1991     $desc .= "\n";
1992     $desc .= OutputSymbolExtraLinks($symbol);
1993     $desc .= &MakeDeprecationNote($symbol);
1995     if (defined ($SymbolDocs{$symbol})) {
1996         $desc .= &ConvertMarkDown($symbol, $SymbolDocs{$symbol});
1997     }
1999     # Create a table of fields and descriptions
2001     my @fields = ParseEnumDeclaration($declaration);
2002     my $params = $SymbolParams{$symbol};
2004     # If nothing at all is documented log a single summary warning at the end.
2005     # Otherwise, warn about each undocumented item.
2007     my $found = 0;
2008     if (defined $params) {
2009         for (my $i = 1; $i <= $#$params; $i += $PARAM_FIELD_COUNT) {
2010             if ($params->[$i] =~ /\S/) {
2011                 $found = 1;
2012                 last;
2013             }
2014         }
2015     }
2017     my %field_descrs = (defined $params ? @$params : ());
2018     my $missing_parameters = "";
2019     my $unused_parameters = "";
2021     $desc .= <<EOF;
2022 <refsect3 role="enum_members">\n<title>Members</title>
2023 <informaltable role="enum_members_table" pgwide="1" frame="none">
2024 <tgroup cols="3">
2025 <colspec colname="enum_members_name" colwidth="300px"/>
2026 <colspec colname="enum_members_description"/>
2027 <colspec colname="enum_members_annotations" colwidth="200px"/>
2028 <tbody>
2031     for my $field_name (@fields) {
2032         my $field_descr = $field_descrs{$field_name};
2033         my $param_annotations = "";
2035         $id = &CreateValidSGMLID ($field_name);
2036         $condition = &MakeConditionDescription ($field_name);
2037         $desc .= "<row role=\"constant\"><entry role=\"enum_member_name\"><para id=\"$id\">$field_name</para></entry>\n";
2038         if (defined $field_descr) {
2039             ($field_descr,$param_annotations) = &ExpandAnnotation($symbol, $field_descr);
2040             $field_descr = &ConvertMarkDown($symbol, $field_descr);
2041             $desc .= "<entry role=\"enum_member_description\">$field_descr</entry>\n<entry role=\"enum_member_annotations\">$param_annotations</entry>\n";
2042             delete $field_descrs{$field_name};
2043         } else {
2044             if ($found) {
2045                 &LogWarning (&GetSymbolSourceFile ($symbol), &GetSymbolSourceLine($symbol),
2046                     "Value description for $symbol"."::"."$field_name is missing in source code comment block.");
2047                 if ($missing_parameters ne "") {
2048                     $missing_parameters .= ", ".$field_name;
2049                 } else {
2050                     $missing_parameters = $field_name;
2051                 }
2052             }
2053             $desc .= "<entry /><entry />\n";
2054         }
2055         $desc .= "</row>\n";
2056     }
2057     $desc .= "</tbody></tgroup></informaltable>\n</refsect3>";
2058     foreach my $field_name (keys %field_descrs) {
2059         &LogWarning (&GetSymbolSourceFile ($symbol), &GetSymbolSourceLine($symbol),
2060             "Value description for $symbol"."::"."$field_name is not used from source code comment block.");
2061         if ($unused_parameters ne "") {
2062             $unused_parameters .= ", ".$field_name;
2063         } else {
2064             $unused_parameters = $field_name;
2065         }
2066     }
2068     # remember missing/unused parameters (needed in tmpl-free build)
2069     if (($missing_parameters ne "") and (! exists ($AllIncompleteSymbols{$symbol}))) {
2070         $AllIncompleteSymbols{$symbol}=$missing_parameters;
2071     }
2072     if (($unused_parameters ne "") and (! exists ($AllUnusedSymbols{$symbol}))) {
2073         $AllUnusedSymbols{$symbol}=$unused_parameters;
2074     }
2076     if (!$found) {
2077         if (scalar(@fields) > 0) {
2078             if (! exists ($AllIncompleteSymbols{$symbol})) {
2079                 $AllIncompleteSymbols{$symbol}="<items>";
2080                 &LogWarning (&GetSymbolSourceFile ($symbol), &GetSymbolSourceLine($symbol),
2081                     "Value descriptions for $symbol are missing in source code comment block.");
2082             }
2083         }
2084     }
2086     $desc .= OutputSymbolTraits ($symbol);
2087     $desc .= "</refsect2>\n";
2088     return ($synop, $desc);
2092 #############################################################################
2093 # Function    : OutputVariable
2094 # Description : Returns the synopsis and detailed description of a variable.
2095 # Arguments   : $symbol - the extern'ed variable.
2096 #                $declaration - the declaration of the variable.
2097 #############################################################################
2099 sub OutputVariable {
2100     my ($symbol, $declaration) = @_;
2101     my $id = &CreateValidSGMLID ($symbol);
2102     my $condition = &MakeConditionDescription ($symbol);
2103     
2104     @TRACE@("ouputing variable: '$symbol' '$declaration'");
2106     my $type_output;
2107     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*;/) {
2108         my $mod1 = defined ($1) ? $1 : "";
2109         my $ptr = defined ($3) ? $3 : "";
2110         my $space = defined ($4) ? $4 : "";
2111         my $mod2 = defined ($5) ? $5 : "";
2112         $type_output = "extern $mod1$ptr$space$mod2";
2113     } 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*=/) {
2114         my $mod1 = defined ($1) ? $1 : "";
2115         my $ptr = defined ($3) ? $3 : "";
2116         my $space = defined ($4) ? $4 : "";
2117         my $mod2 = defined ($5) ? $5 : "";
2118         $type_output = "$mod1$ptr$space$mod2";
2119     } else {
2120         $type_output = "extern";
2121     }
2122     my $synop = "<row><entry role=\"variable_type\">${type_output}</entry><entry role=\"function_name\"><link linkend=\"$id\">$symbol</link></entry></row>\n";
2124     my $desc = "<refsect2 id=\"$id\" role=\"variable\"$condition>\n<title>$symbol</title>\n";
2126     $desc .= MakeIndexterms($symbol, $id);
2127     $desc .= "\n";
2128     $desc .= OutputSymbolExtraLinks($symbol);
2130     my $decl_out = &CreateValidSGML ($declaration);
2131     $desc .= "<programlisting language=\"C\">$decl_out</programlisting>\n";
2133     $desc .= &MakeDeprecationNote($symbol);
2135     if (defined ($SymbolDocs{$symbol})) {
2136         $desc .= &ConvertMarkDown($symbol, $SymbolDocs{$symbol});
2137     }
2138     $desc .= OutputSymbolTraits ($symbol);
2139     $desc .= "</refsect2>\n";
2140     return ($synop, $desc);
2144 #############################################################################
2145 # Function    : OutputFunction
2146 # Description : Returns the synopsis and detailed description of a function.
2147 # Arguments   : $symbol - the function.
2148 #                $declaration - the declaration of the function.
2149 #############################################################################
2151 sub OutputFunction {
2152     my ($symbol, $declaration, $symbol_type) = @_;
2153     my $id = &CreateValidSGMLID ($symbol);
2154     my $condition = &MakeConditionDescription ($symbol);
2156     # Take out the return type     $1                                                                                       $2   $3
2157     $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//;
2158     my $type_modifier = defined($1) ? $1 : "";
2159     my $type = $2;
2160     my $pointer = $3;
2161     # Trim trailing spaces as we are going to pad to $RETURN_TYPE_FIELD_WIDTH below anyway
2162     $pointer =~ s/\s+$//;
2163     my $xref = &MakeXRef ($type, &tagify($type, "returnvalue"));
2164     my $start = "";
2165     #if ($symbol_type eq 'USER_FUNCTION') {
2166     #    $start = "typedef ";
2167     #}
2169     # We output const rather than G_CONST_RETURN.
2170     $type_modifier =~ s/G_CONST_RETURN/const/g;
2171     $pointer =~ s/G_CONST_RETURN/const/g;
2172     $pointer =~ s/^\s+/&#160;/g;
2174     my $ret_type_output;
2175     $ret_type_output = "$start$type_modifier$xref$pointer\n";
2177     my $indent_len;
2178     $indent_len = length ($symbol) + 2;
2179     my $char1 = my $char2 = my $char3 = "";
2180     if ($symbol_type eq 'USER_FUNCTION') {
2181         $indent_len += 3;
2182         $char1 = "<phrase role=\"c_punctuation\">(</phrase>";
2183         $char2 = "*";
2184         $char3 = "<phrase role=\"c_punctuation\">)</phrase>";
2185     }
2187     my ($symbol_output, $symbol_desc_output);
2188     $symbol_output = "$char1<link linkend=\"$id\">$char2$symbol</link>$char3";
2189     if ($indent_len < $MAX_SYMBOL_FIELD_WIDTH) {
2190         $symbol_desc_output = "$char1$char2$symbol$char3 ";
2191     } else {
2192         $indent_len = $MAX_SYMBOL_FIELD_WIDTH - 8;
2193         $symbol_desc_output = "$char1$char2$symbol$char3\n"
2194           . (' ' x ($indent_len - 1));
2195     }
2197     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";
2199     my $desc = "<refsect2 id=\"$id\" role=\"function\"$condition>\n<title>${symbol}&#160;()</title>\n";
2201     $desc .= MakeIndexterms($symbol, $id);
2202     $desc .= "\n";
2203     $desc .= OutputSymbolExtraLinks($symbol);
2205     $desc  .= "<programlisting language=\"C\">${ret_type_output}$symbol_desc_output(";
2207     my @fields = ParseFunctionDeclaration($declaration, \&MakeXRef,
2208                                         sub {
2209                                             &tagify($_[0],"parameter");
2210                                         });
2212     for (my $i = 1; $i <= $#fields; $i += 2) {
2213         my $field_name = $fields[$i];
2215         if ($i == 1) {
2216             $desc  .= "$field_name";
2217         } else {
2218             $desc  .= ",\n"
2219                 . (' ' x $indent_len)
2220                 . "$field_name";
2221         }
2223     }
2225     $desc  .= ");</programlisting>\n";
2227     $desc .= &MakeDeprecationNote($symbol);
2229     if (defined ($SymbolDocs{$symbol})) {
2230         my $symbol_docs = &ConvertMarkDown($symbol, $SymbolDocs{$symbol});
2231         $desc .= $symbol_docs;
2232     }
2233     if (defined ($SymbolAnnotations{$symbol})) {
2234       my $param_desc = $SymbolAnnotations{$symbol};
2235       my $param_annotations = "";
2236       @TRACE@("expand annotation for $symbol: $param_desc");
2237       ($param_desc,$param_annotations) = &ExpandAnnotation($symbol, $param_desc);
2238       @TRACE@("expanded annotation for $symbol: $param_desc | $param_annotations");
2239       if ($param_annotations ne "") {
2240          $desc .= "\n<para>$param_annotations</para>";
2241       }
2242     }
2244     $desc .= &OutputParamDescriptions ("FUNCTION", $symbol, @fields);
2245     $desc .= OutputSymbolTraits ($symbol);
2246     $desc .= "</refsect2>\n";
2247     return ($synop, $desc);
2251 #############################################################################
2252 # Function    : OutputParamDescriptions
2253 # Description : Returns the DocBook output describing the parameters of a
2254 #                function, macro or signal handler.
2255 # Arguments   : $symbol_type - 'FUNCTION', 'MACRO' or 'SIGNAL'. Signal
2256 #                  handlers have an implicit user_data parameter last.
2257 #                $symbol - the name of the function/macro being described.
2258 #               @fields - parsed fields from the declaration, used to determine
2259 #                  undocumented/unused entries
2260 #############################################################################
2262 sub OutputParamDescriptions {
2263     my ($symbol_type, $symbol, @fields) = @_;
2264     my $output = "";
2265     my $params = $SymbolParams{$symbol};
2266     my $num_params = 0;
2267     my %field_descrs = ();
2269     if (@fields) {
2270         %field_descrs = @fields;
2271         delete $field_descrs{"void"};
2272         delete $field_descrs{"Returns"};
2273     }
2275     if (defined $params) {
2276         my $returns = "";
2277         my $params_desc = "";
2278         my $missing_parameters = "";
2279         my $unused_parameters = "";
2280         my $j;
2282         for ($j = 0; $j <= $#$params; $j += $PARAM_FIELD_COUNT) {
2283             my $param_name = $$params[$j];
2284             my $param_desc = $$params[$j + 1];
2285             my $param_annotations = "";
2287             ($param_desc,$param_annotations) = &ExpandAnnotation($symbol, $param_desc);
2288             $param_desc = &ConvertMarkDown($symbol, $param_desc);
2289             # trim
2290             $param_desc =~ s/^(\s|\n)+//msg;
2291             $param_desc =~ s/(\s|\n)+$//msg;
2292             if ($param_name eq "Returns") {
2293                 $returns = $param_desc;
2294                 if ($param_annotations ne "") {
2295                     $returns .= "\n<para>$param_annotations</para>";
2296                 }
2297             } elsif ($param_name eq "void") {
2298                 # FIXME: &LogWarning()?
2299                 @TRACE@("!!!! void in params for $symbol?\n");
2300             } else {
2301                 if (@fields) {
2302                     if (!defined $field_descrs{$param_name}) {
2303                         &LogWarning (&GetSymbolSourceFile ($symbol), &GetSymbolSourceLine($symbol),
2304                             "Parameter description for $symbol"."::"."$param_name is not used from source code comment block.");
2305                         if ($unused_parameters ne "") {
2306                           $unused_parameters .= ", ".$param_name;
2307                         } else {
2308                            $unused_parameters = $param_name;
2309                         }
2310                     } else {
2311                         delete $field_descrs{$param_name};
2312                     }
2313                 }
2314                 if($param_desc ne "") {
2315                     $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";
2316                     $num_params++;
2317                 }
2318             }
2319         }
2320         foreach my $param_name (keys %field_descrs) {
2321             &LogWarning (&GetSymbolSourceFile ($symbol), &GetSymbolSourceLine($symbol),
2322                 "Parameter description for $symbol"."::"."$param_name is missing in source code comment block.");
2323             if ($missing_parameters ne "") {
2324               $missing_parameters .= ", ".$param_name;
2325             } else {
2326                $missing_parameters = $param_name;
2327             }
2328         }
2330         # Signals have an implicit user_data parameter which we describe.
2331         if ($symbol_type eq "SIGNAL") {
2332             $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";
2333         }
2335         # Start a table if we need one.
2336         if ($params_desc ne "") {
2337           $output .= <<EOF;
2338 <refsect3 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           $output .= <<EOF;
2353 <refsect3 role=\"returns\">\n<title>Returns</title>
2355           $output .= $returns;
2356           $output .= "\n</refsect3>";
2357         }
2359         # remember missing/unused parameters (needed in tmpl-free build)
2360         if (($missing_parameters ne "") and (! exists ($AllIncompleteSymbols{$symbol}))) {
2361             $AllIncompleteSymbols{$symbol}=$missing_parameters;
2362         }
2363         if (($unused_parameters ne "") and (! exists ($AllUnusedSymbols{$symbol}))) {
2364             $AllUnusedSymbols{$symbol}=$unused_parameters;
2365         }
2366     }
2367     if (($num_params == 0) && @fields && (scalar(keys(%field_descrs)) > 0)) {
2368         if (! exists ($AllIncompleteSymbols{$symbol})) {
2369             $AllIncompleteSymbols{$symbol}="<parameters>";
2370         }
2371     }
2373     return $output;
2377 #############################################################################
2378 # Function    : ParseStabilityLevel
2379 # Description : Parses a stability level and outputs a warning if it isn't
2380 #               valid.
2381 # Arguments   : $stability - the stability text.
2382 #                $file, $line - context for error message
2383 #                $message - description of where the level is from, to use in
2384 #               any error message.
2385 # Returns     : The parsed stability level string.
2386 #############################################################################
2388 sub ParseStabilityLevel {
2389     my ($stability, $file, $line, $message) = @_;
2391     $stability =~ s/^\s*//;
2392     $stability =~ s/\s*$//;
2393     if ($stability =~ m/^stable$/i) {
2394         $stability = "Stable";
2395     } elsif ($stability =~ m/^unstable$/i) {
2396         $stability = "Unstable";
2397     } elsif ($stability =~ m/^private$/i) {
2398         $stability = "Private";
2399     } else {
2400         &LogWarning ($file, $line, "$message is $stability.".
2401             "It should be one of these: Stable, Unstable, or Private.");
2402     }
2403     return $stability;
2407 #############################################################################
2408 # Function    : OutputSGMLFile
2409 # Description : Outputs the final DocBook file for one section.
2410 # Arguments   : $file - the name of the file.
2411 #               $title - the title from the $MODULE-sections.txt file, which
2412 #                 will be overridden by the title in the template file.
2413 #               $section_id - the SGML id to use for the toplevel tag.
2414 #               $includes - comma-separates list of include files added at top of
2415 #                 synopsis, with '<' '>' around them (if not already enclosed in "").
2416 #               $functions_synop - reference to the DocBook for the Functions Synopsis part.
2417 #               $other_synop - reference to the DocBook for the Types and Values Synopsis part.
2418 #               $functions_details - reference to the DocBook for the Functions Details part.
2419 #               $other_details - reference to the DocBook for the Types and Values Details part.
2420 #               $signal_synop - reference to the DocBook for the Signal Synopsis part
2421 #               $signal_desc - reference to the DocBook for the Signal Description part
2422 #               $args_synop - reference to the DocBook for the Arg Synopsis part
2423 #               $args_desc - reference to the DocBook for the Arg Description part
2424 #               $hierarchy - reference to the DocBook for the Object Hierarchy part
2425 #               $interfaces - reference to the DocBook for the Interfaces part
2426 #               $implementations - reference to the DocBook for the Known Implementations part
2427 #               $prerequisites - reference to the DocBook for the Prerequisites part
2428 #               $derived - reference to the DocBook for the Derived Interfaces part
2429 #               $file_objects - reference to an array of objects in this file
2430 #############################################################################
2432 sub OutputSGMLFile {
2433     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) = @_;
2435     @TRACE@("Output sgml for file $file with title '$title'\n");
2437     # The edited title overrides the one from the sections file.
2438     my $new_title = $SymbolDocs{"$TMPL_DIR/$file:Title"};
2439     if (defined ($new_title) && $new_title !~ m/^\s*$/) {
2440         $title = $new_title;
2441         @TRACE@("Found title: $title\n");
2442     }
2443     my $short_desc = $SymbolDocs{"$TMPL_DIR/$file:Short_Description"};
2444     if (!defined ($short_desc) || $short_desc =~ m/^\s*$/) {
2445         $short_desc = "";
2446     } else {
2447         # Don't use ConvertMarkDown here for now since we don't want blocks
2448         $short_desc = &ExpandAbbreviations("$title:Short_description",
2449                                            $short_desc);
2450         @TRACE@("Found short_desc: $short_desc");
2451     }
2452     my $long_desc = $SymbolDocs{"$TMPL_DIR/$file:Long_Description"};
2453     if (!defined ($long_desc) || $long_desc =~ m/^\s*$/) {
2454         $long_desc = "";
2455     } else {
2456         $long_desc = &ConvertMarkDown("$title:Long_description",
2457                                           $long_desc);
2458         @TRACE@("Found long_desc: $long_desc");
2459     }
2460     my $see_also = $SymbolDocs{"$TMPL_DIR/$file:See_Also"};
2461     if (!defined ($see_also) || $see_also =~ m%^\s*(<para>)?\s*(</para>)?\s*$%) {
2462         $see_also = "";
2463     } else {
2464         $see_also = &ConvertMarkDown("$title:See_Also", $see_also);
2465         @TRACE@("Found see_also: $see_also");
2466     }
2467     if ($see_also) {
2468         $see_also = "<refsect1 id=\"$section_id.see-also\">\n<title>See Also</title>\n$see_also\n</refsect1>\n";
2469     }
2470     my $stability = $SymbolDocs{"$TMPL_DIR/$file:Stability_Level"};
2471     if (!defined ($stability) || $stability =~ m/^\s*$/) {
2472         $stability = "";
2473     } else {
2474         $stability = &ParseStabilityLevel($stability, $file, $., "Section stability level");
2475         @TRACE@("Found stability: $stability");
2476     }
2477     if ($stability) {
2478         $AnnotationsUsed{$stability} = 1;
2479         $stability = "<refsect1 id=\"$section_id.stability-level\">\n<title>Stability Level</title>\n<acronym>$stability</acronym>, unless otherwise indicated\n</refsect1>\n";
2480     } elsif ($DEFAULT_STABILITY) {
2481         $AnnotationsUsed{$DEFAULT_STABILITY} = 1;
2482         $stability = "<refsect1 id=\"$section_id.stability-level\">\n<title>Stability Level</title>\n<acronym>$DEFAULT_STABILITY</acronym>, unless otherwise indicated\n</refsect1>\n";
2483     }
2485     my $image = $SymbolDocs{"$TMPL_DIR/$file:Image"};
2486     if (!defined ($image) || $image =~ m/^\s*$/) {
2487       $image = "";
2488     } else {
2489       $image =~ s/^\s*//;
2490       $image =~ s/\s*$//;
2492       my $format;
2494       if ($image =~ /jpe?g$/i) {
2495         $format = "format='JPEG'";
2496       } elsif ($image =~ /png$/i) {
2497         $format = "format='PNG'";
2498       } elsif ($image =~ /svg$/i) {
2499         $format = "format='SVG'";
2500       } else {
2501         $format = "";
2502       }
2504       $image = "  <inlinegraphic fileref='$image' $format/>\n"
2505     }
2507     my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) =
2508         gmtime (time);
2509     my $month = (qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec))[$mon];
2510     $year += 1900;
2512     my $include_output = "";
2513     if ($includes) {
2514       $include_output .= "<refsect1 id=\"$section_id.includes\"><title>Includes</title><synopsis>";
2515       my $include;
2516       foreach $include (split (/,/, $includes)) {
2517         if ($include =~ m/^\".+\"$/) {
2518           $include_output .= "#include ${include}\n";
2519         }
2520         else {
2521           $include =~ s/^\s+|\s+$//gs;
2522           $include_output .= "#include &lt;${include}&gt;\n";
2523         }
2524       }
2525       $include_output .= "</synopsis></refsect1>\n";
2526     }
2528     my $extralinks = OutputSectionExtraLinks($title,"Section:$file");
2530     my $old_sgml_file = "$SGML_OUTPUT_DIR/$file.$OUTPUT_FORMAT";
2531     my $new_sgml_file = "$SGML_OUTPUT_DIR/$file.$OUTPUT_FORMAT.new";
2533     open (OUTPUT, ">$new_sgml_file")
2534         || die "Can't create $new_sgml_file: $!";
2536     my $object_anchors = "";
2537     foreach my $object (@$file_objects) {
2538         next if ($object eq $section_id);
2539         my $id = CreateValidSGMLID($object);
2540         @TRACE@("Adding anchor for $object\n");
2541         $object_anchors .= "<anchor id=\"$id\"$empty_element_end";
2542     }
2544     # We used to output this, but is messes up our UpdateFileIfChanged code
2545     # since it changes every day (and it is only used in the man pages):
2546     # "<refentry id="$section_id" revision="$mday $month $year">"
2548     if ($OUTPUT_FORMAT eq "xml") {
2549         print OUTPUT $doctype_header;
2550     }
2552     print OUTPUT <<EOF;
2553 <refentry id="$section_id">
2554 <refmeta>
2555 <refentrytitle role="top_of_page" id="$section_id.top_of_page">$title</refentrytitle>
2556 <manvolnum>3</manvolnum>
2557 <refmiscinfo>
2558   \U$MODULE\E Library
2559 $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_sgml_file, $new_sgml_file, 0);
2589 #############################################################################
2590 # Function    : OutputExtraFile
2591 # Description : Copies an "extra" DocBook file into the output directory,
2592 #               expanding abbreviations
2593 # Arguments   : $file - the source file.
2594 #############################################################################
2595 sub OutputExtraFile {
2596     my ($file) = @_;
2598     my $basename;
2600     ($basename = $file) =~ s!^.*/!!;
2602     my $old_sgml_file = "$SGML_OUTPUT_DIR/$basename";
2603     my $new_sgml_file = "$SGML_OUTPUT_DIR/$basename.new";
2605     my $contents;
2607     open(EXTRA_FILE, "<$file") || die "Can't open $file";
2609     {
2610         local $/;
2611         $contents = <EXTRA_FILE>;
2612     }
2614     open (OUTPUT, ">$new_sgml_file")
2615         || die "Can't create $new_sgml_file: $!";
2617     print OUTPUT &ExpandAbbreviations ("$basename file", $contents);
2618     close (OUTPUT);
2620     return &UpdateFileIfChanged ($old_sgml_file, $new_sgml_file, 0);
2622 #############################################################################
2623 # Function    : OutputBook
2624 # Description : Outputs the SGML entities that need to be included into the
2625 #                main SGML file for the module.
2626 # Arguments   : $book_top - the declarations of the entities, which are added
2627 #                  at the top of the main SGML file.
2628 #                $book_bottom - the references to the entities, which are
2629 #                  added in the main SGML file at the desired position.
2630 #############################################################################
2632 sub OutputBook {
2633     my ($book_top, $book_bottom) = @_;
2635     my $old_file = "$SGML_OUTPUT_DIR/$MODULE-doc.top";
2636     my $new_file = "$SGML_OUTPUT_DIR/$MODULE-doc.top.new";
2638     open (OUTPUT, ">$new_file")
2639         || die "Can't create $new_file: $!";
2640     print OUTPUT $book_top;
2641     close (OUTPUT);
2643     &UpdateFileIfChanged ($old_file, $new_file, 0);
2646     $old_file = "$SGML_OUTPUT_DIR/$MODULE-doc.bottom";
2647     $new_file = "$SGML_OUTPUT_DIR/$MODULE-doc.bottom.new";
2649     open (OUTPUT, ">$new_file")
2650         || die "Can't create $new_file: $!";
2651     print OUTPUT $book_bottom;
2652     close (OUTPUT);
2654     &UpdateFileIfChanged ($old_file, $new_file, 0);
2657     # If the main SGML/XML file hasn't been created yet, we create it here.
2658     # The user can tweak it later.
2659     if ($MAIN_SGML_FILE && ! -e $MAIN_SGML_FILE) {
2660       open (OUTPUT, ">$MAIN_SGML_FILE")
2661         || die "Can't create $MAIN_SGML_FILE: $!";
2663       if ($OUTPUT_FORMAT eq "xml") {
2664           print OUTPUT <<EOF;
2665 <?xml version="1.0"?>
2666 <!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.3//EN"
2667                "http://www.oasis-open.org/docbook/xml/4.3/docbookx.dtd"
2669   <!ENTITY % local.common.attrib "xmlns:xi  CDATA  #FIXED 'http://www.w3.org/2003/XInclude'">
2671 <book id="index">
2673       } else {
2674         print OUTPUT <<EOF;
2675 <!doctype book PUBLIC "-//Davenport//DTD DocBook V3.0//EN" [
2676 $book_top
2678 <book id="index">
2680       }
2682 print OUTPUT <<EOF;
2683   <bookinfo>
2684     <title>$MODULE Reference Manual</title>
2685     <releaseinfo>
2686       for $MODULE [VERSION].
2687       The latest version of this documentation can be found on-line at
2688       <ulink role="online-location" url="http://[SERVER]/$MODULE/index.html">http://[SERVER]/$MODULE/</ulink>.
2689     </releaseinfo>
2690   </bookinfo>
2692   <chapter>
2693     <title>[Insert title here]</title>
2694     $book_bottom
2695   </chapter>
2697   if (-e $OBJECT_TREE_FILE) {
2698     print OUTPUT <<EOF;
2699   <chapter id="object-tree">
2700     <title>Object Hierarchy</title>
2701      <xi:include href="xml/tree_index.sgml"/>
2702   </chapter>
2704   }
2706 print OUTPUT <<EOF;
2707   <index id="api-index-full">
2708     <title>API Index</title>
2709     <xi:include href="xml/api-index-full.xml"><xi:fallback /></xi:include>
2710   </index>
2711   <index id="deprecated-api-index" role="deprecated">
2712     <title>Index of deprecated API</title>
2713     <xi:include href="xml/api-index-deprecated.xml"><xi:fallback /></xi:include>
2714   </index>
2716   <xi:include href="xml/annotation-glossary.xml"><xi:fallback /></xi:include>
2717 </book>
2720       close (OUTPUT);
2721     }
2725 #############################################################################
2726 # Function    : CreateValidSGML
2727 # Description : This turns any chars which are used in SGML into entities,
2728 #                e.g. '<' into '&lt;'
2729 # Arguments   : $text - the text to turn into proper SGML.
2730 #############################################################################
2732 sub CreateValidSGML {
2733     my ($text) = @_;
2734     $text =~ s/&/&amp;/g;        # Do this first, or the others get messed up.
2735     $text =~ s/</&lt;/g;
2736     $text =~ s/>/&gt;/g;
2737     # browers render single tabs inconsistently
2738     $text =~ s/([^\s])\t([^\s])/$1&#160;$2/g;
2739     return $text;
2742 #############################################################################
2743 # Function    : ConvertSGMLChars
2744 # Description : This is used for text in source code comment blocks, to turn
2745 #               chars which are used in SGML into entities, e.g. '<' into
2746 #               '&lt;'. Depending on $INLINE_MARKUP_MODE, this is done
2747 #               unconditionally or only if the character doesn't seem to be
2748 #               part of an SGML construct (tag or entity reference).
2749 # Arguments   : $text - the text to turn into proper SGML.
2750 #############################################################################
2752 sub ConvertSGMLChars {
2753     my ($symbol, $text) = @_;
2755     if ($INLINE_MARKUP_MODE) {
2756         # For the XML/SGML mode only convert to entities outside CDATA sections.
2757         return &ModifyXMLElements ($text, $symbol,
2758                                    "<!\\[CDATA\\[|<programlisting[^>]*>",
2759                                    \&ConvertSGMLCharsEndTag,
2760                                    \&ConvertSGMLCharsCallback);
2761     } else {
2762         # For the simple non-sgml mode, convert to entities everywhere.
2764         # First, convert freestanding & to &amp;
2765         $text =~ s/&(?![a-zA-Z#]+;)/&amp;/g;
2766         $text =~ s/</&lt;/g;
2767         # Allow ">" at beginning of string for blockquote markdown
2768         $text =~ s/(?<=[^\w\n"'\/-])>/&gt;/g;
2770         return $text;
2771     }
2775 sub ConvertSGMLCharsEndTag {
2776   if ($_[0] eq "<!\[CDATA\[") {
2777     return "]]>";
2778   } else {
2779     return "</programlisting>";
2780   }
2783 sub ConvertSGMLCharsCallback {
2784   my ($text, $symbol, $tag) = @_;
2786   if ($tag =~ m/^<programlisting/) {
2787     # We can handle <programlisting> specially here.
2788     return &ModifyXMLElements ($text, $symbol,
2789                                "<!\\[CDATA\\[",
2790                                \&ConvertSGMLCharsEndTag,
2791                                \&ConvertSGMLCharsCallback2);
2792   } elsif ($tag eq "") {
2793     # If we're not in CDATA convert to entities.
2794     $text =~ s/&(?![a-zA-Z#]+;)/&amp;/g;        # Do this first, or the others get messed up.
2795     $text =~ s/<(?![a-zA-Z\/!])/&lt;/g;
2796     # Allow ">" at beginning of string for blockquote markdown
2797     $text =~ s/(?<=[^\w\n"'\/-])>/&gt;/g;
2799     # Handle "#include <xxxxx>"
2800     $text =~ s/#include(\s+)<([^>]+)>/#include$1&lt;$2&gt;/g;
2801   }
2803   return $text;
2806 sub ConvertSGMLCharsCallback2 {
2807   my ($text, $symbol, $tag) = @_;
2809   # If we're not in CDATA convert to entities.
2810   # We could handle <programlisting> differently, though I'm not sure it helps.
2811   if ($tag eq "") {
2812     # replace only if its not a tag
2813     $text =~ s/&(?![a-zA-Z#]+;)/&amp;/g;        # Do this first, or the others get messed up.
2814     $text =~ s/<(?![a-zA-Z\/!])/&lt;/g;
2815     $text =~ s/(?<![a-zA-Z0-9"'\/-])>/&gt;/g;
2817     # Handle "#include <xxxxx>"
2818     $text =~ s/#include(\s+)<([^>]+)>/#include$1&lt;$2&gt;/g;
2819   }
2821   return $text;
2824 #############################################################################
2825 # Function    : ExpandAnnotation
2826 # Description : This turns annotations into acronym tags.
2827 # Arguments   : $symbol - the symbol being documented, for error messages.
2828 #                $text - the text to expand.
2829 #############################################################################
2830 sub ExpandAnnotation {
2831     my ($symbol, $param_desc) = @_;
2832     my $param_annotations = "";
2834     # look for annotations at the start of the comment part
2835     # function level annotations don't end with a colon ':'
2836     if ($param_desc =~ m%^\s*\((.*?)\)(:|$)%) {
2837         my @annotations;
2838         my $annotation;
2839         $param_desc = $';
2841         @annotations = split(/\)\s*\(/,$1);
2842         foreach $annotation (@annotations) {
2843             # need to search for the longest key-match in %AnnotationDefinition
2844             my $match_length=0;
2845             my $match_annotation="";
2846             my $annotationdef;
2847             foreach $annotationdef (keys %AnnotationDefinition) {
2848                 if ($annotation =~ m/^$annotationdef/) {
2849                     if (length($annotationdef)>$match_length) {
2850                         $match_length=length($annotationdef);
2851                         $match_annotation=$annotationdef;
2852                     }
2853                 }
2854             }
2855             my $annotation_extra = "";
2856             if ($match_annotation ne "") {
2857                 if ($annotation =~ m%$match_annotation\s+(.*)%) {
2858                     $annotation_extra = " $1";
2859                 }
2860                 $AnnotationsUsed{$match_annotation} = 1;
2861                 $param_annotations .= "[<acronym>$match_annotation</acronym>$annotation_extra]";
2862             }
2863             else {
2864                 &LogWarning (&GetSymbolSourceFile ($symbol), &GetSymbolSourceLine($symbol),
2865                     "unknown annotation \"$annotation\" in documentation for $symbol.");
2866                 $param_annotations .= "[$annotation]";
2867             }
2868         }
2869         chomp($param_desc);
2870         $param_desc =~ m/^(.*?)\.*\s*$/s;
2871         $param_desc = "$1. ";
2872     }
2873     if ($param_annotations ne "") {
2874         $param_annotations = "<emphasis role=\"annotation\">$param_annotations</emphasis>";
2875     }
2876     return ($param_desc, $param_annotations);
2879 #############################################################################
2880 # Function    : ExpandAbbreviations
2881 # Description : This turns the abbreviations function(), macro(), @param,
2882 #                %constant, and #symbol into appropriate DocBook markup.
2883 #               CDATA sections and <programlisting> parts are skipped.
2884 # Arguments   : $symbol - the symbol being documented, for error messages.
2885 #                $text - the text to expand.
2886 #############################################################################
2888 sub ExpandAbbreviations {
2889   my ($symbol, $text) = @_;
2891   # Note: This is a fallback and normally done in the markdown parser
2893   # Convert "|[" and "]|" into the start and end of program listing examples.
2894   # Support \[<!-- language="C" --> modifiers
2895   $text =~ s%\|\[<!-- language="([^"]+)" -->%<informalexample><programlisting language="$1"><![CDATA[%g;
2896   $text =~ s%\|\[%<informalexample><programlisting><![CDATA[%g;
2897   $text =~ s%\]\|%]]></programlisting></informalexample>%g;
2899   # keep CDATA unmodified, preserve ulink tags (ideally we preseve all tags
2900   # as such)
2901   return &ModifyXMLElements ($text, $symbol,
2902                              "<!\\[CDATA\\[|<ulink[^>]*>|<programlisting[^>]*>|<!DOCTYPE",
2903                              \&ExpandAbbreviationsEndTag,
2904                              \&ExpandAbbreviationsCallback);
2908 # Returns the end tag (as a regexp) corresponding to the given start tag.
2909 sub ExpandAbbreviationsEndTag {
2910   my ($start_tag) = @_;
2912   if ($start_tag eq "<!\[CDATA\[") {
2913     return "]]>";
2914   } elsif ($start_tag eq "<!DOCTYPE") {
2915     return ">";
2916   } elsif ($start_tag =~ m/<(\w+)/) {
2917     return "</$1>";
2918   }
2921 # Called inside or outside each CDATA or <programlisting> section.
2922 sub ExpandAbbreviationsCallback {
2923   my ($text, $symbol, $tag) = @_;
2925   if ($tag =~ m/^<programlisting/) {
2926     # Handle any embedded CDATA sections.
2927     return &ModifyXMLElements ($text, $symbol,
2928                                "<!\\[CDATA\\[",
2929                                \&ExpandAbbreviationsEndTag,
2930                                \&ExpandAbbreviationsCallback2);
2931   } elsif ($tag eq "") {
2932     # NOTE: this is a fallback. It is normally done by the Markdown parser.
2934     # We are outside any CDATA or <programlisting> sections, so we expand
2935     # any gtk-doc abbreviations.
2937     # Convert '@param()'
2938     # FIXME: we could make those also links ($symbol.$2), but that would be less
2939     # useful as the link target is a few lines up or down
2940     $text =~ s/(\A|[^\\])\@(\w+((\.|->)\w+)*)\s*\(\)/$1<parameter>$2()<\/parameter>/g;
2942     # Convert 'function()' or 'macro()'.
2943     # if there is abc_*_def() we don't want to make a link to _def()
2944     # FIXME: also handle abc(def(....)) : but that would need to be done recursively :/
2945     $text =~ s/([^\*.\w])(\w+)\s*\(\)/$1.&MakeXRef($2, &tagify($2 . "()", "function"));/eg;
2946     # handle #Object.func()
2947     $text =~ s/(\A|[^\\])#([\w\-:\.]+[\w]+)\s*\(\)/$1.&MakeXRef($2, &tagify($2 . "()", "function"));/eg;
2949     # Convert '@param', but not '\@param'.
2950     $text =~ s/(\A|[^\\])\@(\w+((\.|->)\w+)*)/$1<parameter>$2<\/parameter>/g;
2951     $text =~ s/\\\@/\@/g;
2953     # Convert '%constant', but not '\%constant'.
2954     # Also allow negative numbers, e.g. %-1.
2955     $text =~ s/(\A|[^\\])\%(-?\w+)/$1.&MakeXRef($2, &tagify($2, "literal"));/eg;
2956     $text =~ s/\\\%/\%/g;
2958     # Convert '#symbol', but not '\#symbol'.
2959     $text =~ s/(\A|[^\\])#([\w\-:\.]+[\w]+)/$1.&MakeHashXRef($2, "type");/eg;
2960     $text =~ s/\\#/#/g;
2961   }
2963   return $text;
2966 # This is called inside a <programlisting>
2967 sub ExpandAbbreviationsCallback2 {
2968   my ($text, $symbol, $tag) = @_;
2970   if ($tag eq "") {
2971     # We are inside a <programlisting> but outside any CDATA sections,
2972     # so we expand any gtk-doc abbreviations.
2973     # FIXME: why is this different from &ExpandAbbreviationsCallback(),
2974     #        why not just call it
2975     $text =~ s/#(\w+)/&MakeHashXRef($1, "");/eg;
2976   } elsif ($tag eq "<![CDATA[") {
2977     # NOTE: this is a fallback. It is normally done by the Markdown parser.
2978     $text = &ReplaceEntities ($text, $symbol);
2979   }
2981   return $text;
2984 sub MakeHashXRef {
2985     my ($symbol, $tag) = @_;;
2986     my $text = $symbol;
2988     # Check for things like '#include', '#define', and skip them.
2989     if ($PreProcessorDirectives{$symbol}) {
2990       return "#$symbol";
2991     }
2993     # Get rid of special suffixes ('-struct','-enum').
2994     $text =~ s/-struct$//;
2995     $text =~ s/-enum$//;
2997     # If the symbol is in the form "Object::signal", then change the symbol to
2998     # "Object-signal" and use "signal" as the text.
2999     if ($symbol =~ s/::/-/) {
3000       $text = "“$'”";
3001     }
3003     # If the symbol is in the form "Object:property", then change the symbol to
3004     # "Object--property" and use "property" as the text.
3005     if ($symbol =~ s/:/--/) {
3006       $text = "“$'”";
3007     }
3009     if ($tag ne "") {
3010       $text = tagify ($text, $tag);
3011     }
3013     return &MakeXRef($symbol, $text);
3017 #############################################################################
3018 # Function    : ModifyXMLElements
3019 # Description : Looks for given XML element tags within the text, and calls
3020 #               the callback on pieces of text inside & outside those elements.
3021 #               Used for special handling of text inside things like CDATA
3022 #               and <programlisting>.
3023 # Arguments   : $text - the text.
3024 #               $symbol - the symbol currently being documented (only used for
3025 #                      error messages).
3026 #               $start_tag_regexp - the regular expression to match start tags.
3027 #                      e.g. "<!\\[CDATA\\[|<programlisting[^>]*>" to match
3028 #                      CDATA sections or programlisting elements.
3029 #               $end_tag_func - function which is passed the matched start tag
3030 #                      and should return the appropriate end tag string regexp.
3031 #               $callback - callback called with each part of the text. It is
3032 #                      called with a piece of text, the symbol being
3033 #                      documented, and the matched start tag or "" if the text
3034 #                      is outside the XML elements being matched.
3035 #############################################################################
3036 sub ModifyXMLElements {
3037     my ($text, $symbol, $start_tag_regexp, $end_tag_func, $callback) = @_;
3038     my ($before_tag, $start_tag, $end_tag_regexp, $end_tag);
3039     my $result = "";
3041     while ($text =~ m/$start_tag_regexp/s) {
3042       $before_tag = $`; # Prematch for last successful match string
3043       $start_tag = $&;  # Last successful match
3044       $text = $';       # Postmatch for last successful match string
3046       $result .= &$callback ($before_tag, $symbol, "");
3047       $result .= $start_tag;
3049       # get the matching end-tag for current tag
3050       $end_tag_regexp = &$end_tag_func ($start_tag);
3052       if ($text =~ m/$end_tag_regexp/s) {
3053         $before_tag = $`;
3054         $end_tag = $&;
3055         $text = $';
3057         $result .= &$callback ($before_tag, $symbol, $start_tag);
3058         $result .= $end_tag;
3059       } else {
3060         &LogWarning (&GetSymbolSourceFile ($symbol), &GetSymbolSourceLine($symbol),
3061             "Can't find tag end: $end_tag_regexp in docs for: $symbol.");
3062         # Just assume it is all inside the tag.
3063         $result .= &$callback ($text, $symbol, $start_tag);
3064         $text = "";
3065       }
3066     }
3068     # Handle any remaining text outside the tags.
3069     $result .= &$callback ($text, $symbol, "");
3071     return $result;
3074 sub noop {
3075   return $_[0];
3078 # Adds a tag around some text.
3079 # e.g tagify("Text", "literal") => "<literal>Text</literal>".
3080 sub tagify {
3081    my ($text, $elem) = @_;
3082    return "<" . $elem . ">" . $text . "</" . $elem . ">";
3086 #############################################################################
3087 # Function    : MakeXRef
3088 # Description : This returns a cross-reference link to the given symbol.
3089 #                Though it doesn't try to do this for a few standard C types
3090 #                that it        knows won't be in the documentation.
3091 # Arguments   : $symbol - the symbol to try to create a XRef to.
3092 #               $text - text text to put inside the XRef, defaults to $symbol
3093 #############################################################################
3095 sub MakeXRef {
3096     my ($symbol, $text) = ($_[0], $_[1]);
3098     $symbol =~ s/^\s+//;
3099     $symbol =~ s/\s+$//;
3101     if (!defined($text)) {
3102         $text = $symbol;
3104         # Get rid of special suffixes ('-struct','-enum').
3105         $text =~ s/-struct$//;
3106         $text =~ s/-enum$//;
3107     }
3109     if ($symbol =~ m/ /) {
3110         return "$text";
3111     }
3113     @TRACE@("Getting type link for $symbol -> $text\n");
3115     my $symbol_id = &CreateValidSGMLID ($symbol);
3116     return "<link linkend=\"$symbol_id\">$text</link>";
3120 #############################################################################
3121 # Function    : MakeIndexterms
3122 # Description : This returns a indexterm elements for the given symbol
3123 # Arguments   : $symbol - the symbol to create indexterms for
3124 #############################################################################
3126 sub MakeIndexterms {
3127   my ($symbol, $id) = @_;
3128   my $terms =  "";
3129   my $sortas = "";
3131   # make the index useful, by ommiting the namespace when sorting
3132   if ($NAME_SPACE ne "") {
3133     if ($symbol =~ m/^$NAME_SPACE\_?(.*)/i) {
3134        $sortas=" sortas=\"$1\"";
3135     }
3136   }
3138   if (exists $Deprecated{$symbol}) {
3139       $terms .= "<indexterm zone=\"$id\" role=\"deprecated\"><primary$sortas>$symbol</primary></indexterm>";
3140       $IndexEntriesDeprecated{$symbol}=$id;
3141       $IndexEntriesFull{$symbol}=$id;
3142   }
3143   if (exists $Since{$symbol}) {
3144      my $since = $Since{$symbol};
3145      $since =~ s/^\s+//;
3146      $since =~ s/\s+$//;
3147      if ($since ne "") {
3148          $terms .= "<indexterm zone=\"$id\" role=\"$since\"><primary$sortas>$symbol</primary></indexterm>";
3149      }
3150      $IndexEntriesSince{$symbol}=$id;
3151      $IndexEntriesFull{$symbol}=$id;
3152   }
3153   if ($terms eq "") {
3154      $terms .= "<indexterm zone=\"$id\"><primary$sortas>$symbol</primary></indexterm>";
3155      $IndexEntriesFull{$symbol}=$id;
3156   }
3158   return $terms;
3161 #############################################################################
3162 # Function    : MakeDeprecationNote
3163 # Description : This returns a deprecation warning for the given symbol.
3164 # Arguments   : $symbol - the symbol to try to create a warning for.
3165 #############################################################################
3167 sub MakeDeprecationNote {
3168     my ($symbol) = $_[0];
3169     my $desc = "";
3170     if (exists $Deprecated{$symbol}) {
3171         my $note;
3173         $desc .= "<warning><para><literal>$symbol</literal> ";
3175         $note = $Deprecated{$symbol};
3177         if ($note =~ /^\s*([0-9\.]+)\s*:?/) {
3178                 $desc .= "has been deprecated since version $1 and should not be used in newly-written code.</para>";
3179         } else {
3180                 $desc .= "is deprecated and should not be used in newly-written code.</para>";
3181         }
3182         $note =~ s/^\s*([0-9\.]+)\s*:?\s*//;
3183         $note =~ s/^\s+//;
3184         $note =~ s/\s+$//;
3185         if ($note ne "") {
3186             $note = &ConvertMarkDown($symbol, $note);
3187             $desc .= " " . $note;
3188         }
3189         $desc .= "</warning>\n";
3190     }
3191     return $desc;
3194 #############################################################################
3195 # Function    : MakeConditionDescription
3196 # Description : This returns a sumary of conditions for the given symbol.
3197 # Arguments   : $symbol - the symbol to try to create the sumary.
3198 #############################################################################
3200 sub MakeConditionDescription {
3201     my ($symbol) = $_[0];
3202     my $desc = "";
3204     if (exists $Deprecated{$symbol}) {
3205         if ($desc ne "") {
3206             $desc .= "|";
3207         }
3209         if ($Deprecated{$symbol} =~ /^\s*(.*?)\s*$/) {
3210                 $desc .= "deprecated:$1";
3211         } else {
3212                 $desc .= "deprecated";
3213         }
3214     }
3216     if (exists $Since{$symbol}) {
3217         if ($desc ne "") {
3218             $desc .= "|";
3219         }
3221         if ($Since{$symbol} =~ /^\s*(.*?)\s*$/) {
3222                 $desc .= "since:$1";
3223         } else {
3224                 $desc .= "since";
3225         }
3226     }
3228     if (exists $StabilityLevel{$symbol}) {
3229         if ($desc ne "") {
3230             $desc .= "|";
3231         }
3232         $desc .= "stability:".$StabilityLevel{$symbol};
3233     }
3235     if ($desc ne "") {
3236         my $cond = $desc;
3237         $cond =~ s/\"/&quot;/g;
3238         $desc=" condition=\"".$cond."\"";
3239         @TRACE@("condition for '$symbol' = '$desc'\n");
3240     }
3241     return $desc;
3244 #############################################################################
3245 # Function    : GetHierarchy
3246 # Description : Returns the DocBook output describing the ancestors and
3247 #               immediate children of a GObject subclass. It uses the
3248 #               global @Objects and @ObjectLevels arrays to walk the tree.
3250 # Arguments   : $object - the GtkObject subclass.
3251 #               @hierarchy - previous hierarchy
3252 #############################################################################
3254 sub GetHierarchy {
3255     my ($object,$hierarchy_ref) = @_;
3256     my @hierarchy = @{$hierarchy_ref};
3257     
3258     # Find object in the objects array.
3259     my $found = 0;
3260     my @children = ();
3261     my $i;
3262     my $level;
3263     my $j;
3264     for ($i = 0; $i < @Objects; $i++) {
3265         if ($found) {
3266             if ($ObjectLevels[$i] <= $level) {
3267             last;
3268         }
3269             elsif ($ObjectLevels[$i] == $level + 1) {
3270                 push (@children, $Objects[$i]);
3271             }
3272         }
3273         elsif ($Objects[$i] eq $object) {
3274             $found = 1;
3275             $j = $i;
3276             $level = $ObjectLevels[$i];
3277         }
3278     }
3279     if (!$found) {
3280         return @hierarchy;
3281     }
3283     # Walk up the hierarchy, pushing ancestors onto the ancestors array.
3284     my @ancestors = ();
3285     push (@ancestors, $object);
3286     @TRACE@("Level: $level\n");
3287     while ($level > 1) {
3288         $j--;
3289         if ($ObjectLevels[$j] < $level) {
3290             push (@ancestors, $Objects[$j]);
3291             $level = $ObjectLevels[$j];
3292             @TRACE@("Level: $level\n");
3293         }
3294     }
3296     # Output the ancestors, indented and with links.
3297     my $last_index = 0;
3298     $level = 1;
3299     for ($i = $#ancestors; $i >= 0; $i--) {
3300         my $link_text;
3301         # Don't add a link to the current object, i.e. when i == 0.
3302         if ($i > 0) {
3303             my $ancestor_id = &CreateValidSGMLID ($ancestors[$i]);
3304             $link_text = "<link linkend=\"$ancestor_id\">$ancestors[$i]</link>";
3305         } else {
3306             $link_text = "$ancestors[$i]";
3307         }
3308         my $indented_text = ' ' x ($level * 4) . $link_text;
3309         # Check if we already have this object
3310         my $index = -1;
3311         for ($j = 0; $j <= $#hierarchy; $j++) {
3312             if ($hierarchy[$j] eq $indented_text) {
3313                 $index = $j;
3314                 last;
3315             }
3316         }
3317         if ($index == -1) {
3318             # We have a new entry, find insert position in alphabetical order
3319             my $indent = ' ' x ($level * 4);
3320             my $found = 0;
3321             for ($j = $last_index; $j <= $#hierarchy; $j++) {
3322                 if ($hierarchy[$j] !~ m/^${indent}/) {
3323                     $last_index = $j;
3324                     $found = 1;
3325                     last;
3326                 } elsif ($hierarchy[$j] =~ m/^${indent}[^ ]/) {
3327                     my $stripped_text = $hierarchy[$j];
3328                     if ($indented_text !~ m/<link linkend/) {
3329                         $stripped_text =~ s%<link linkend="[A-Za-z]*">%%;
3330                         $stripped_text =~ s%</link>%%;
3331                     }
3332                     if ($indented_text lt $stripped_text) {
3333                         $last_index = $j;
3334                         $found = 1;
3335                         last;
3336                     } 
3337                 }
3338             }
3339             if (!$found) {
3340               $last_index = 1 + $#hierarchy;
3341             }
3342             splice @hierarchy, $last_index, 0, ($indented_text);
3343             $last_index++;
3344         } else {
3345             # Already have this one, remmeber index as base insert point
3346             $last_index = $index + 1;
3347         }
3348         $level++;
3349     }
3350     # Output the children, indented and with links.
3351     for ($i = 0; $i <= $#children; $i++) {
3352         my $id = &CreateValidSGMLID ($children[$i]);
3353         my $indented_text = ' ' x ($level * 4) . "<link linkend=\"$id\">$children[$i]</link>";
3354         splice @hierarchy, $last_index, 0, ($indented_text);
3355         $last_index++;
3356     }    
3358     return @hierarchy; 
3361 #############################################################################
3362 # Function    : GetInterfaces
3363 # Description : Returns the DocBook output describing the interfaces
3364 #               implemented by a class. It uses the global %Interfaces hash.
3365 # Arguments   : $object - the GtkObject subclass.
3366 #############################################################################
3368 sub GetInterfaces {
3369     my ($object) = @_;
3370     my $text = "";
3371     my $i;
3373     # Find object in the objects array.
3374     if (exists($Interfaces{$object})) {
3375         my @ifaces = split(' ', $Interfaces{$object});
3376         $text = <<EOF;
3377 <para>
3378 $object implements
3380         for ($i = 0; $i <= $#ifaces; $i++) {
3381             my $id = &CreateValidSGMLID ($ifaces[$i]);
3382             $text .= " <link linkend=\"$id\">$ifaces[$i]</link>";
3383             if ($i < $#ifaces - 1) {
3384                 $text .= ', ';
3385             }
3386             elsif ($i < $#ifaces) {
3387                 $text .= ' and ';
3388             }
3389             else {
3390                 $text .= '.';
3391             }
3392         }
3393         $text .= <<EOF;
3394 </para>
3396     }
3398     return $text;
3401 #############################################################################
3402 # Function    : GetImplementations
3403 # Description : Returns the DocBook output describing the implementations
3404 #               of an interface. It uses the global %Interfaces hash.
3405 # Arguments   : $object - the GtkObject subclass.
3406 #############################################################################
3408 sub GetImplementations {
3409     my ($object) = @_;
3410     my @impls = ();
3411     my $text = "";
3412     my $i;
3413     foreach my $key (keys %Interfaces) {
3414         if ($Interfaces{$key} =~ /\b$object\b/) {
3415             push (@impls, $key);
3416         }
3417     }
3418     if ($#impls >= 0) {
3419         @impls = sort @impls;
3420         $text = <<EOF;
3421 <para>
3422 $object is implemented by
3424         for ($i = 0; $i <= $#impls; $i++) {
3425             my $id = &CreateValidSGMLID ($impls[$i]);
3426             $text .= " <link linkend=\"$id\">$impls[$i]</link>";
3427             if ($i < $#impls - 1) {
3428                 $text .= ', ';
3429             }
3430             elsif ($i < $#impls) {
3431                 $text .= ' and ';
3432             }
3433             else {
3434                 $text .= '.';
3435             }
3436         }
3437         $text .= <<EOF;
3438 </para>
3440     }
3441     return $text;
3445 #############################################################################
3446 # Function    : GetPrerequisites
3447 # Description : Returns the DocBook output describing the prerequisites
3448 #               of an interface. It uses the global %Prerequisites hash.
3449 # Arguments   : $iface - the interface.
3450 #############################################################################
3452 sub GetPrerequisites {
3453     my ($iface) = @_;
3454     my $text = "";
3455     my $i;
3457     if (exists($Prerequisites{$iface})) {
3458         $text = <<EOF;
3459 <para>
3460 $iface requires
3462         my @prereqs = split(' ', $Prerequisites{$iface});
3463         for ($i = 0; $i <= $#prereqs; $i++) {
3464             my $id = &CreateValidSGMLID ($prereqs[$i]);
3465             $text .= " <link linkend=\"$id\">$prereqs[$i]</link>";
3466             if ($i < $#prereqs - 1) {
3467                 $text .= ', ';
3468             }
3469             elsif ($i < $#prereqs) {
3470                 $text .= ' and ';
3471             }
3472             else {
3473                 $text .= '.';
3474             }
3475         }
3476         $text .= <<EOF;
3477 </para>
3479     }
3480     return $text;
3483 #############################################################################
3484 # Function    : GetDerived
3485 # Description : Returns the DocBook output describing the derived interfaces
3486 #               of an interface. It uses the global %Prerequisites hash.
3487 # Arguments   : $iface - the interface.
3488 #############################################################################
3490 sub GetDerived {
3491     my ($iface) = @_;
3492     my $text = "";
3493     my $i;
3495     my @derived = ();
3496     foreach my $key (keys %Prerequisites) {
3497         if ($Prerequisites{$key} =~ /\b$iface\b/) {
3498             push (@derived, $key);
3499         }
3500     }
3501     if ($#derived >= 0) {
3502         @derived = sort @derived;
3503         $text = <<EOF;
3504 <para>
3505 $iface is required by
3507         for ($i = 0; $i <= $#derived; $i++) {
3508             my $id = &CreateValidSGMLID ($derived[$i]);
3509             $text .= " <link linkend=\"$id\">$derived[$i]</link>";
3510             if ($i < $#derived - 1) {
3511                 $text .= ', ';
3512             }
3513             elsif ($i < $#derived) {
3514                 $text .= ' and ';
3515             }
3516             else {
3517                 $text .= '.';
3518             }
3519         }
3520         $text .= <<EOF;
3521 </para>
3523     }
3524     return $text;
3528 #############################################################################
3529 # Function    : GetSignals
3530 # Description : Returns the synopsis and detailed description DocBook output
3531 #                for the signal handlers of a given GtkObject subclass.
3532 # Arguments   : $object - the GtkObject subclass, e.g. 'GtkButton'.
3533 #############################################################################
3535 sub GetSignals {
3536     my ($object) = @_;
3537     my $synop = "";
3538     my $desc = "";
3540     my $i;
3541     for ($i = 0; $i <= $#SignalObjects; $i++) {
3542         if ($SignalObjects[$i] eq $object) {
3543             @TRACE@("Found signal: $SignalNames[$i]\n");
3544             my $name = $SignalNames[$i];
3545             my $symbol = "${object}::${name}";
3546             my $id = &CreateValidSGMLID ("$object-$name");
3548             $desc .= "<refsect2 id=\"$id\" role=\"signal\"><title>The <literal>“$name”</literal> signal</title>\n";
3549             $desc .= MakeIndexterms($symbol, $id);
3550             $desc .= "\n";
3551             $desc .= OutputSymbolExtraLinks($symbol);
3553             $desc .= "<programlisting language=\"C\">";
3555             $SignalReturns[$i] =~ m/\s*(const\s+)?(\w+)\s*(\**)/;
3556             my $type_modifier = defined($1) ? $1 : "";
3557             my $type = $2;
3558             my $pointer = $3;
3559             my $xref = &MakeXRef ($type, &tagify($type, "returnvalue"));
3561             my $ret_type_output = "$type_modifier$xref$pointer";
3562             my $callback_name = "user_function";
3563             $desc  .= "${ret_type_output}\n${callback_name} (";
3565             my $indentation = ' ' x (length($callback_name) + 2);
3566             my $pad = $indentation;
3568             my $sourceparams = $SourceSymbolParams{$symbol};
3569             my @params = split ("\n", $SignalPrototypes[$i]);
3570             my $j;
3571             my $l;
3572             my $type_len = length("gpointer");
3573             my $name_len = length("user_data");
3574             # do two passes, the first one is to calculate padding
3575             for ($l = 0; $l < 2; $l++) {
3576                 for ($j = 0; $j <= $#params; $j++) {
3577                     my $param_name;
3578                     # allow alphanumerics, '_', '[' & ']' in param names
3579                     if ($params[$j] =~ m/^\s*(\w+)\s*(\**)\s*([\w\[\]]+)\s*$/) {
3580                         $type = $1;
3581                         $pointer = $2;
3582                         if (defined($sourceparams)) {
3583                             $param_name = $$sourceparams[$PARAM_FIELD_COUNT * $j];
3584                         }
3585                         else {
3586                             $param_name = $3;
3587                         }
3588                         if (!defined($param_name)) {
3589                             $param_name = "arg$j";
3590                         }
3591                         if ($l == 0) {
3592                             if (length($type) + length($pointer) > $type_len) {
3593                                 $type_len = length($type) + length($pointer);
3594                             }
3595                             if (length($param_name) > $name_len) {
3596                                 $name_len = length($param_name);
3597                             }
3598                         }
3599                         else {
3600                             $xref = &MakeXRef ($type, &tagify($type, "type"));
3601                             $pad = ' ' x ($type_len - length($type) - length($pointer));
3602                             $desc .= "$xref$pad $pointer${param_name},\n";
3603                             $desc .= $indentation;
3604                         }
3605                     } else {
3606                         &LogWarning (&GetSymbolSourceFile ($symbol), &GetSymbolSourceLine($symbol),
3607                              "Can't parse arg: $params[$j]\nArgs:$SignalPrototypes[$i]");
3608                     }
3609                 }
3610             }
3611             $xref = &MakeXRef ("gpointer", &tagify("gpointer", "type"));
3612             $pad = ' ' x ($type_len - length("gpointer"));
3613             $desc  .= "$xref$pad user_data)";
3614             $desc  .= "</programlisting>\n";
3616             my $flags = $SignalFlags[$i];
3617             my $flags_string = "";
3619             if (defined ($flags)) {
3620               if ($flags =~ m/f/) {
3621                 $flags_string = "<link linkend=\"G-SIGNAL-RUN-FIRST:CAPS\">Run First</link>";
3622               }
3623               elsif ($flags =~ m/l/) {
3624                 $flags_string = "<link linkend=\"G-SIGNAL-RUN-LAST:CAPS\">Run Last</link>";
3625               }
3626               elsif ($flags =~ m/c/) {
3627                 $flags_string = "<link linkend=\"G-SIGNAL-RUN-CLEANUP:CAPS\">Cleanup</link>";
3628                 $flags_string = "Cleanup";
3629               }
3630               if ($flags =~ m/r/) {
3631                 if ($flags_string) { $flags_string .= " / "; }
3632                 $flags_string = "<link linkend=\"G-SIGNAL-NO-RECURSE:CAPS\">No Recursion</link>";
3633               }
3634               if ($flags =~ m/d/) {
3635                 if ($flags_string) { $flags_string .= " / "; }
3636                 $flags_string = "<link linkend=\"G-SIGNAL-DETAILED:CAPS\">Has Details</link>";
3637               }
3638               if ($flags =~ m/a/) {
3639                 if ($flags_string) { $flags_string .= " / "; }
3640                 $flags_string = "<link linkend=\"G-SIGNAL-ACTION:CAPS\">Action</link>";
3641               }
3642               if ($flags =~ m/h/) {
3643                 if ($flags_string) { $flags_string .= " / "; }
3644                 $flags_string = "<link linkend=\"G-SIGNAL-NO-HOOKS:CAPS\">No Hooks</link>";
3645               }
3646             }
3648             $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";
3650             my $parameters = &OutputParamDescriptions ("SIGNAL", $symbol);
3652             $AllSymbols{$symbol} = 1;
3653             if (defined ($SymbolDocs{$symbol})) {
3654                 my $symbol_docs = &ConvertMarkDown($symbol, $SymbolDocs{$symbol});
3656                 $desc .= $symbol_docs;
3658                 if (!IsEmptyDoc($SymbolDocs{$symbol})) {
3659                     $AllDocumentedSymbols{$symbol} = 1;
3660                 }
3661             }
3662             $desc .= &MakeDeprecationNote($symbol);
3664             $desc .= $parameters;
3665             if ($flags_string) {
3666                 $desc  .= "<para>Flags: $flags_string</para>\n";
3667             }
3668             $desc .= OutputSymbolTraits ($symbol);
3669             $desc .= "</refsect2>";
3670         }
3671     }
3672     return ($synop, $desc);
3676 #############################################################################
3677 # Function    : GetArgs
3678 # Description : Returns the synopsis and detailed description DocBook output
3679 #                for the Args of a given GtkObject subclass.
3680 # Arguments   : $object - the GtkObject subclass, e.g. 'GtkButton'.
3681 #############################################################################
3683 sub GetArgs {
3684     my ($object) = @_;
3685     my $synop = "";
3686     my $desc = "";
3687     my $child_synop = "";
3688     my $child_desc = "";
3689     my $style_synop = "";
3690     my $style_desc = "";
3692     my $i;
3693     for ($i = 0; $i <= $#ArgObjects; $i++) {
3694         if ($ArgObjects[$i] eq $object) {
3695             @TRACE@("Found arg: $ArgNames[$i]\n");
3696             my $name = $ArgNames[$i];
3697             my $flags = $ArgFlags[$i];
3698             my $flags_string = "";
3699             my $kind = "";
3700             my $id_sep = "";
3702             if ($flags =~ m/c/) {
3703                 $kind = "child property";
3704                 $id_sep = "c-";
3705             }
3706             elsif ($flags =~ m/s/) {
3707                 $kind = "style property";
3708                 $id_sep = "s-";
3709             }
3710             else {
3711                 $kind = "property";
3712             }
3714             # Remember only one colon so we don't clash with signals.
3715             my $symbol = "${object}:${name}";
3716             # use two dashes and ev. an extra separator here for the same reason.
3717             my $id = &CreateValidSGMLID ("$object--$id_sep$name");
3719             my $type = $ArgTypes[$i];
3720             my $type_output;
3721             my $range = $ArgRanges[$i];
3722             my $range_output = CreateValidSGML ($range);
3723             my $default = $ArgDefaults[$i];
3724             my $default_output = CreateValidSGML ($default);
3726             if ($type eq "GtkString") {
3727                 $type = "char&#160;*";
3728             }
3729             if ($type eq "GtkSignal") {
3730                 $type = "GtkSignalFunc, gpointer";
3731                 $type_output = &MakeXRef ("GtkSignalFunc") . ", "
3732                     . &MakeXRef ("gpointer");
3733             } elsif ($type =~ m/^(\w+)\*$/) {
3734                 $type_output = &MakeXRef ($1, &tagify($1, "type")) . "&#160;*";
3735             } else {
3736                 $type_output = &MakeXRef ($type, &tagify($type, "type"));
3737             }
3739             if ($flags =~ m/r/) {
3740                 $flags_string = "Read";
3741             }
3742             if ($flags =~ m/w/) {
3743                 if ($flags_string) { $flags_string .= " / "; }
3744                 $flags_string .= "Write";
3745             }
3746             if ($flags =~ m/x/) {
3747                 if ($flags_string) { $flags_string .= " / "; }
3748                 $flags_string .= "Construct";
3749             }
3750             if ($flags =~ m/X/) {
3751                 if ($flags_string) { $flags_string .= " / "; }
3752                 $flags_string .= "Construct Only";
3753             }
3755             $AllSymbols{$symbol} = 1;
3756             my $blurb;
3757             if (defined($SymbolDocs{$symbol}) &&
3758                 !IsEmptyDoc($SymbolDocs{$symbol})) {
3759                 $blurb = &ConvertMarkDown($symbol, $SymbolDocs{$symbol});
3760                 @TRACE@(".. [$SymbolDocs{$symbol}][$blurb]\n");
3761                 $AllDocumentedSymbols{$symbol} = 1;
3762             }
3763             else {
3764                 if (!($ArgBlurbs[$i] eq "")) {
3765                     $AllDocumentedSymbols{$symbol} = 1;
3766                 } else {
3767                     # FIXME: print a warning?
3768                     @TRACE@(".. no description\n");
3769                 }
3770                 $blurb = "<para>" . &CreateValidSGML ($ArgBlurbs[$i]) . "</para>";
3771             }
3773             my $pad1 = " " x (24 - length ($name));
3775             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";
3776             my $arg_desc = "<refsect2 id=\"$id\" role=\"property\"><title>The <literal>“$name”</literal> $kind</title>\n";
3777             $arg_desc .= MakeIndexterms($symbol, $id);
3778             $arg_desc .= "\n";
3779             $arg_desc .= OutputSymbolExtraLinks($symbol);
3781             $arg_desc .= "<programlisting>  “$name”$pad1 $type_output</programlisting>\n";
3782             $arg_desc .= $blurb;
3783             $arg_desc .= &MakeDeprecationNote($symbol);
3785             if ($flags_string) {
3786               $arg_desc  .= "<para>Flags: $flags_string</para>\n";
3787             }
3788             if ($range ne "") {
3789                 $arg_desc .= "<para>Allowed values: $range_output</para>\n";
3790             }
3791             if ($default ne "") {
3792                 $arg_desc .= "<para>Default value: $default_output</para>\n";
3793             }
3794             $arg_desc .= OutputSymbolTraits ($symbol);
3795             $arg_desc .= "</refsect2>\n";
3797             if ($flags =~ m/c/) {
3798                 $child_synop .= $arg_synop;
3799                 $child_desc .= $arg_desc;
3800             }
3801             elsif ($flags =~ m/s/) {
3802                 $style_synop .= $arg_synop;
3803                 $style_desc .= $arg_desc;
3804             }
3805             else {
3806                 $synop .= $arg_synop;
3807                 $desc .= $arg_desc;
3808             }
3809         }
3810     }
3811     return ($synop, $child_synop, $style_synop, $desc, $child_desc, $style_desc);
3815 #############################################################################
3816 # Function    : ReadSourceDocumentation
3817 # Description : This reads in the documentation embedded in comment blocks
3818 #                in the source code (for Gnome).
3820 #                Parameter descriptions override any in the template files.
3821 #                Function descriptions are placed before any description from
3822 #                the template files.
3824 #                It recursively descends the source directory looking for .c
3825 #                files and scans them looking for specially-formatted comment
3826 #                blocks.
3828 # Arguments   : $source_dir - the directory to scan.
3829 #############m###############################################################
3831 sub ReadSourceDocumentation {
3832     my ($source_dir) = @_;
3833     my ($file, $dir, @suffix_list, $suffix);
3835     # prepend entries from @SOURCE_DIR
3836     for my $dir (@SOURCE_DIRS) {
3837         # Check if the filename is in the ignore list.
3838         if ($source_dir =~ m%^\Q$dir\E/(.*)$% and $IGNORE_FILES =~ m/(\s|^)\Q$1\E(\s|$)/) {
3839             @TRACE@("Skipping source directory: $source_dir");
3840             return;
3841         } else {
3842             @TRACE@("No match for: ".($1 || $source_dir));
3843         }
3844     }
3846     @TRACE@("Scanning source directory: $source_dir");
3848     # This array holds any subdirectories found.
3849     my (@subdirs) = ();
3851     @suffix_list = split (/,/, $SOURCE_SUFFIXES);
3853     opendir (SRCDIR, $source_dir)
3854         || die "Can't open source directory $source_dir: $!";
3856     foreach $file (readdir (SRCDIR)) {
3857       if ($file =~ /^\./) {
3858         next;
3859       } elsif (-d "$source_dir/$file") {
3860         push (@subdirs, $file);
3861       } elsif (@suffix_list) {
3862         foreach $suffix (@suffix_list) {
3863           if ($file =~ m/\.\Q${suffix}\E$/) {
3864             &ScanSourceFile ("$source_dir/$file");
3865           }
3866         }
3867       } elsif ($file =~ m/\.[ch]$/) {
3868         &ScanSourceFile ("$source_dir/$file");
3869       }
3870     }
3871     closedir (SRCDIR);
3873     # Now recursively scan the subdirectories.
3874     foreach $dir (@subdirs) {
3875         &ReadSourceDocumentation ("$source_dir/$dir");
3876     }
3880 #############################################################################
3881 # Function    : ScanSourceFile
3882 # Description : Scans one source file looking for specially-formatted comment
3883 #                blocks. Later &MergeSourceDocumentation is used to merge any
3884 #                documentation found with the documentation already read in
3885 #                from the template files.
3887 # Arguments   : $file - the file to scan.
3888 #############################################################################
3890 sub ScanSourceFile {
3891     my ($file) = @_;
3892     my $basename;
3894     # prepend entries from @SOURCE_DIR
3895     for my $dir (@SOURCE_DIRS) {
3896         # Check if the filename is in the ignore list.
3897         if ($file =~ m%^\Q$dir\E/(.*)$% and $IGNORE_FILES =~ m/(\s|^)\Q$1\E(\s|$)/) {
3898             @TRACE@("Skipping source file: $file");
3899             return;
3900         }
3901     }
3903     if ($file =~ m/^.*[\/\\]([^\/\\]*)$/) {
3904         $basename = $1;
3905     } else {
3906         &LogWarning ($file, 1, "Can't find basename for this filename.");
3907         $basename = $file;
3908     }
3910     # Check if the basename is in the list of files to ignore.
3911     if ($IGNORE_FILES =~ m/(\s|^)\Q${basename}\E(\s|$)/) {
3912         @TRACE@("Skipping source file: $file");
3913         return;
3914     }
3916     @TRACE@("Scanning source file: $file");
3918     open (SRCFILE, $file)
3919         || die "Can't open $file: $!";
3920     my $in_comment_block = 0;
3921     my $symbol;
3922     my $in_part = "";
3923     my ($description, $return_desc);
3924     my ($since_desc, $stability_desc, $deprecated_desc);
3925     my $current_param;
3926     my @params;
3927     while (<SRCFILE>) {
3928         # Look for the start of a comment block.
3929         if (!$in_comment_block) {
3930             if (m%^\s*/\*.*\*/%) {
3931                 #one-line comment - not gtkdoc
3932             } elsif (m%^\s*/\*\*\s%) {
3933                 @TRACE@("Found comment block start\n");
3935                 $in_comment_block = 1;
3937                 # Reset all the symbol data.
3938                 $symbol = "";
3939                 $in_part = "";
3940                 $description = "";
3941                 $return_desc = "";
3942                 $since_desc = "";
3943                 $deprecated_desc = "";
3944                 $stability_desc = "";
3945                 $current_param = -1;
3946                 @params = ();
3947             }
3948             next;
3949         }
3951         # We're in a comment block. Check if we've found the end of it.
3952         if (m%^\s*\*+/%) {
3953             if (!$symbol) {
3954                 # maybe its not even meant to be a gtk-doc comment?
3955                 &LogWarning ($file, $., "Symbol name not found at the start of the comment block.");
3956             } else {
3957                 # Add the return value description onto the end of the params.
3958                 if ($return_desc) {
3959                     # TODO(ensonic): check for duplicated Return docs
3960                     # &LogWarning ($file, $., "Multiple Returns for $symbol.");
3961                     push (@params, "Returns");
3962                     push (@params, $return_desc);
3963                 }
3964                 # Convert special SGML characters
3965                 $description = &ConvertSGMLChars ($symbol, $description);
3966                 my $k;
3967                 for ($k = 1; $k <= $#params; $k += $PARAM_FIELD_COUNT) {
3968                     $params[$k] = &ConvertSGMLChars ($symbol, $params[$k]);
3969                 }
3971                 # Handle Section docs
3972                 if ($symbol =~ m/SECTION:\s*(.*)/) {
3973                     my $real_symbol=$1;
3974                     my $key;
3976                     if (scalar %KnownSymbols) {
3977                         if ((! defined($KnownSymbols{"$TMPL_DIR/$real_symbol:Long_Description"})) || $KnownSymbols{"$TMPL_DIR/$real_symbol:Long_Description"} != 1) {
3978                             &LogWarning ($file, $., "Section $real_symbol is not defined in the $MODULE-sections.txt file.");
3979                         }
3980                     }
3982                     @TRACE@("SECTION DOCS found in source for : '$real_symbol'\n");
3983                     for ($k = 0; $k <= $#params; $k += $PARAM_FIELD_COUNT) {
3984                         @TRACE@("   '".$params[$k]."'\n");
3985                         $params[$k] = "\L$params[$k]";
3986                         undef $key;
3987                         if ($params[$k] eq "short_description") {
3988                             $key = "$TMPL_DIR/$real_symbol:Short_Description";
3989                         } elsif ($params[$k] eq "see_also") {
3990                             $key = "$TMPL_DIR/$real_symbol:See_Also";
3991                         } elsif ($params[$k] eq "title") {
3992                             $key = "$TMPL_DIR/$real_symbol:Title";
3993                         } elsif ($params[$k] eq "stability") {
3994                             $key = "$TMPL_DIR/$real_symbol:Stability_Level";
3995                         } elsif ($params[$k] eq "section_id") {
3996                             $key = "$TMPL_DIR/$real_symbol:Section_Id";
3997                         } elsif ($params[$k] eq "include") {
3998                             $key = "$TMPL_DIR/$real_symbol:Include";
3999                         } elsif ($params[$k] eq "image") {
4000                             $key = "$TMPL_DIR/$real_symbol:Image";
4001                         }
4002                         if (defined($key)) {
4003                             $SourceSymbolDocs{$key}=$params[$k+1];
4004                             $SourceSymbolSourceFile{$key} = $file;
4005                             $SourceSymbolSourceLine{$key} = $.;
4006                         }
4007                     }
4008                     $SourceSymbolDocs{"$TMPL_DIR/$real_symbol:Long_Description"}=$description;
4009                     $SourceSymbolSourceFile{"$TMPL_DIR/$real_symbol:Long_Description"} = $file;
4010                     $SourceSymbolSourceLine{"$TMPL_DIR/$real_symbol:Long_Description"} = $.;
4011                     #$SourceSymbolTypes{$symbol} = "SECTION";
4012                 } else {
4013                     @TRACE@("SYMBOL DOCS found in source for : '$symbol' ",length($description), "\n");
4014                     $SourceSymbolDocs{$symbol} = $description;
4015                     $SourceSymbolParams{$symbol} = [ @params ];
4016                     # FIXME $SourceSymbolTypes{$symbol} = "STRUCT,SIGNAL,ARG,FUNCTION,MACRO";
4017                     #if (defined $DeclarationTypes{$symbol}) {
4018                     #    $SourceSymbolTypes{$symbol} = $DeclarationTypes{$symbol}
4019                     #}
4020                     $SourceSymbolSourceFile{$symbol} = $file;
4021                     $SourceSymbolSourceLine{$symbol} = $.;
4022                 }                
4024                 if ($since_desc) {
4025                      ($since_desc, my @extra_lines) = split ("\n", $since_desc);
4026                      $since_desc =~ s/^\s+//;
4027                      $since_desc =~ s/\s+$//;
4028                      @TRACE@("Since($symbol) : [$since_desc]\n");
4029                      $Since{$symbol} = &ConvertSGMLChars ($symbol, $since_desc);
4030                      if(scalar @extra_lines) {
4031                          &LogWarning ($file, $., "multi-line since docs found");
4032                      }
4033                 }
4035                 if ($stability_desc) {
4036                     $stability_desc = &ParseStabilityLevel($stability_desc, $file, $., "Stability level for $symbol");
4037                     $StabilityLevel{$symbol} = &ConvertSGMLChars ($symbol, $stability_desc);
4038                 }
4040                 if ($deprecated_desc) {
4041                     if (!exists $Deprecated{$symbol}) {
4042                          # don't warn for signals and properties
4043                          #if ($symbol !~ m/::?(.*)/) {
4044                          if (defined $DeclarationTypes{$symbol}) {
4045                              &LogWarning ($file, $.,
4046                                  "$symbol is deprecated in the inline comments, but no deprecation guards were found around the declaration.".
4047                                  " (See the --deprecated-guards option for gtkdoc-scan.)");
4048                          }
4049                     }
4050                     $Deprecated{$symbol} = &ConvertSGMLChars ($symbol, $deprecated_desc);
4051                 }
4052             }
4054             $in_comment_block = 0;
4055             next;
4056         }
4058         # Get rid of ' * ' at start of every line in the comment block.
4059         s%^\s*\*\s?%%;
4060         # But make sure we don't get rid of the newline at the end.
4061         if (!$_) {
4062             $_ = "\n";
4063         }
4064         @TRACE@("scanning :$_");
4066         # If we haven't found the symbol name yet, look for it.
4067         if (!$symbol) {
4068             if (m%^\s*(SECTION:\s*\S+)%) {
4069                 $symbol = $1;
4070                 @TRACE@("SECTION DOCS found in source for : '$symbol'\n");
4071             } elsif (m%^\s*([\w:-]*\w)\s*:?\s*(\([-A-Za-z0-9._ ]+\)\s*)*$%) {
4072                 $symbol = $1;
4073                 my $annotation = $2;
4074                 @TRACE@("SYMBOL DOCS found in source for : '$symbol'\n");
4075                 if (defined($annotation)) {
4076                     chomp($annotation);
4077                     if ($annotation ne "") {
4078                         $SymbolAnnotations{$symbol} = $annotation;
4079                         @TRACE@("remaining text for $symbol: '$annotation'\n");
4080                     }
4081                 }
4082             }
4083             next;
4084         }
4086         if ($in_part eq "description") {
4087             # Get rid of 'Description:'
4088             s%^\s*Description:%%;
4089         }
4091         if (m%^\s*(returns|return\s+value):%i) {
4092             # we're in param section and have not seen the blank line
4093             if($in_part ne "") {
4094               $return_desc = $';
4095               $in_part = "return";
4096               next;
4097             }
4098         } elsif (m%^\s*since:%i) {
4099             # we're in param section and have not seen the blank line
4100             if($in_part ne "") {
4101               $since_desc = $';
4102               $in_part = "since";
4103               next;
4104             }
4105         } elsif (m%^\s*deprecated:%i) {
4106             # we're in param section and have not seen the blank line
4107             if($in_part ne "") {
4108               $deprecated_desc = $';
4109               $in_part = "deprecated";
4110               next;
4111             }
4112         } elsif (m%^\s*stability:%i) {
4113             $stability_desc = $';
4114             $in_part = "stability";
4115             next;
4116         }
4118         if ($in_part eq "description") {
4119             $description .= $_;
4120             next;
4121         } elsif ($in_part eq "return") {
4122             $return_desc .= $_;
4123             next;
4124         } elsif ($in_part eq "since") {
4125             $since_desc .= $_;
4126             next;
4127         } elsif ($in_part eq "stability") {
4128             $stability_desc .= $_;
4129             next;
4130         } elsif ($in_part eq "deprecated") {
4131             $deprecated_desc .= $_;
4132             next;
4133         }
4135         # We must be in the parameters. Check for the empty line below them.
4136         if (m%^\s*$%) {
4137             $in_part = "description";
4138             next;
4139         }
4141         # Look for a parameter name.
4142         if (m%^\s*@(\S+)\s*:\s*%) {
4143             my $param_name = $1;
4144             my $param_desc = $';
4146             @TRACE@("Found parameter: $param_name\n");
4147             # Allow varargs variations
4148             if ($param_name =~ m/^\.\.\.$/) {
4149                 $param_name = "...";
4150             }
4151             @TRACE@("Found param for symbol $symbol : '$param_name'= '$_'");
4153             push (@params, $param_name);
4154             push (@params, $param_desc);
4155             $current_param += $PARAM_FIELD_COUNT;
4156             $in_part = "param";
4157             next;
4158         } elsif ($in_part eq "") {
4159             @TRACE@("continuation for $symbol annotation '$_'");
4160             my $annotation = $_;
4161             $annotation =~ s/^\s+|\s+$//g ; 
4162             $SymbolAnnotations{$symbol} .= $annotation;
4163         }
4165         # We must be in the middle of a parameter description, so add it on
4166         # to the last element in @params.
4167         if ($current_param == -1) {
4168             &LogWarning ($file, $., "Parsing comment block file : parameter expected.");
4169         } else {
4170             $params[$#params] .= $_;
4171         }
4172     }
4173     close (SRCFILE);
4176 #############################################################################
4177 # Function    : OutputMissingDocumentation
4178 # Description : Outputs report of documentation coverage to a file
4180 # Arguments   : none
4181 #############################################################################
4183 sub OutputMissingDocumentation {
4184     my $old_undocumented_file = "$ROOT_DIR/$MODULE-undocumented.txt";
4185     my $new_undocumented_file = "$ROOT_DIR/$MODULE-undocumented.new";
4187     my $n_documented = 0;
4188     my $n_incomplete = 0;
4189     my $total = 0;
4190     my $symbol;
4191     my $percent;
4192     my $msg;
4193     my $buffer = "";
4194     my $buffer_deprecated = "";
4195     my $buffer_descriptions = "";
4197     open(UNDOCUMENTED, ">$new_undocumented_file")
4198       || die "Can't create $new_undocumented_file";
4200     foreach $symbol (sort (keys (%AllSymbols))) {
4201         # FIXME: should we print LogWarnings for undocumented stuff?
4202         # DEBUG
4203         #my $ssfile = &GetSymbolSourceFile($symbol);
4204         #my $ssline = &GetSymbolSourceLine($symbol);
4205         #my $location = "defined at " . (defined($ssfile)?$ssfile:"?") . ":" . (defined($ssline)?$ssline:"0") . "\n";
4206         # DEBUG
4207         if ($symbol !~ /:(Title|Long_Description|Short_Description|See_Also|Stability_Level|Include|Section_Id|Image)/) {
4208             $total++;
4209             if (exists ($AllDocumentedSymbols{$symbol})) {
4210                 $n_documented++;
4211                 if (exists ($AllIncompleteSymbols{$symbol})) {
4212                     $n_incomplete++;
4213                     $buffer .= $symbol . " (" . $AllIncompleteSymbols{$symbol} . ")\n";
4214                     #$buffer .= "\t0: ".$location;
4215                 }
4216             } elsif (exists $Deprecated{$symbol}) {
4217                 if (exists ($AllIncompleteSymbols{$symbol})) {
4218                     $n_incomplete++;
4219                     $buffer_deprecated .= $symbol . " (" . $AllIncompleteSymbols{$symbol} . ")\n";
4220                     #$buffer .= "\t1a: ".$location;
4221                 } else {
4222                     $buffer_deprecated .= $symbol . "\n";
4223                     #$buffer .= "\t1b: ".$location;
4224                 }
4225             } else {
4226                 if (exists ($AllIncompleteSymbols{$symbol})) {
4227                     $n_incomplete++;
4228                     $buffer .= $symbol . " (" . $AllIncompleteSymbols{$symbol} . ")\n";
4229                     #$buffer .= "\t2a: ".$location;
4230                 } else {
4231                     $buffer .= $symbol . "\n";
4232                     #$buffer .= "\t2b: ".$location;
4233                 }
4234             }
4235         } elsif ($symbol =~ /:(Long_Description|Short_Description)/) {
4236             $total++;
4237             if (((exists ($SymbolDocs{$symbol})) && (length ($SymbolDocs{$symbol}) > 0))
4238             || ((exists ($AllDocumentedSymbols{$symbol})) && (length ($AllDocumentedSymbols{$symbol}) > 0))) {
4239               $n_documented++;
4240             } else {
4241               # cut off the leading namespace ($TMPL_DIR)
4242               $symbol =~ m/^.*\/(.*)$/;
4243               $buffer_descriptions .= $1 . "\n";
4244             }
4245         }
4246     }
4248     $buffer .= "\n" . $buffer_deprecated . "\n" . $buffer_descriptions;
4250     if ($total == 0) {
4251       $percent = 100;
4252     } else {
4253       $percent = ($n_documented / $total) * 100.0;
4254     }
4256     printf UNDOCUMENTED "%.0f%% symbol docs coverage.\n", $percent;
4257     print UNDOCUMENTED "$n_documented symbols documented.\n";
4258     print UNDOCUMENTED "$n_incomplete symbols incomplete.\n";
4259     print UNDOCUMENTED ($total - $n_documented) . " not documented.\n\n\n";
4261     print UNDOCUMENTED $buffer;
4262     close (UNDOCUMENTED);
4264     return &UpdateFileIfChanged ($old_undocumented_file, $new_undocumented_file, 0);
4266     printf "%.0f%% symbol docs coverage", $percent;
4267     print "($n_documented symbols documented, $n_incomplete symbols incomplete, " . ($total - $n_documented) . " not documented)\n";
4268     print "See $MODULE-undocumented.txt for a list of missing docs.\nThe doc coverage percentage doesn't include intro sections.\n";
4272 #############################################################################
4273 # Function    : OutputUndeclaredSymbols
4274 # Description : Outputs symbols that are listed in the section file, but not
4275 #               declaration is found in the sources
4277 # Arguments   : none
4278 #############################################################################
4280 sub OutputUndeclaredSymbols {
4281     my $old_undeclared_file = "$ROOT_DIR/$MODULE-undeclared.txt";
4282     my $new_undeclared_file = "$ROOT_DIR/$MODULE-undeclared.new";
4284     open(UNDECLARED, ">$new_undeclared_file")
4285         || die "Can't create $new_undeclared_file";
4287     if (%UndeclaredSymbols) {
4288         print UNDECLARED (join("\n", sort keys %UndeclaredSymbols));
4289         print UNDECLARED "\n";
4290         print "See $MODULE-undeclared.txt for the list of undeclared symbols.\n"
4291     }
4292     close(UNDECLARED);
4294     return &UpdateFileIfChanged ($old_undeclared_file, $new_undeclared_file, 0);
4297 #############################################################################
4298 # Function    : OutputUnusedSymbols
4299 # Description : Outputs symbols that are documented in comments, but not
4300 #               declared in the sources
4302 # Arguments   : none
4303 #############################################################################
4305 sub OutputUnusedSymbols {
4306     my $num_unused = 0;
4307     my $old_unused_file = "$ROOT_DIR/$MODULE-unused.txt";
4308     my $new_unused_file = "$ROOT_DIR/$MODULE-unused.new";
4310     open (UNUSED, ">$new_unused_file")
4311         || die "Can't open $new_unused_file";
4312     my ($symbol);
4313     foreach $symbol (sort keys (%Declarations)) {
4314         if (!defined ($DeclarationOutput{$symbol})) {
4315             print (UNUSED "$symbol\n");
4316             $num_unused++;
4317         }
4318     }
4319     foreach $symbol (sort (keys (%AllUnusedSymbols))) {
4320         print (UNUSED "$symbol(" . $AllUnusedSymbols{$symbol} . ")\n");
4321         $num_unused++;
4322     }
4323     close (UNUSED);
4324     if ($num_unused != 0) {
4325         &LogWarning ($old_unused_file, 1, "$num_unused unused declarations.".
4326             "They should be added to $MODULE-sections.txt in the appropriate place.");
4327     }
4329     return &UpdateFileIfChanged ($old_unused_file, $new_unused_file, 0);
4333 #############################################################################
4334 # Function    : OutputAllSymbols
4335 # Description : Outputs list of all symbols to a file
4337 # Arguments   : none
4338 #############################################################################
4340 sub OutputAllSymbols {
4341      my $n_documented = 0;
4342      my $total = 0;
4343      my $symbol;
4344      my $percent;
4345      my $msg;
4347      open (SYMBOLS, ">$ROOT_DIR/$MODULE-symbols.txt")
4348           || die "Can't create $ROOT_DIR/$MODULE-symbols.txt: $!";
4350      foreach $symbol (sort (keys (%AllSymbols))) {
4351           print SYMBOLS $symbol . "\n";
4352      }
4354      close (SYMBOLS);
4357 #############################################################################
4358 # Function    : OutputSymbolsWithoutSince
4359 # Description : Outputs list of all symbols without a since tag to a file
4361 # Arguments   : none
4362 #############################################################################
4364 sub OutputSymbolsWithoutSince {
4365      my $n_documented = 0;
4366      my $total = 0;
4367      my $symbol;
4368      my $percent;
4369      my $msg;
4371      open (SYMBOLS, ">$ROOT_DIR/$MODULE-nosince.txt")
4372           || die "Can't create $ROOT_DIR/$MODULE-nosince.txt: $!";
4374      foreach $symbol (sort (keys (%SourceSymbolDocs))) {
4375          if (!defined $Since{$symbol}) {
4376              print SYMBOLS $symbol . "\n";
4377          }
4378      }
4380      close (SYMBOLS);
4384 #############################################################################
4385 # Function    : MergeSourceDocumentation
4386 # Description : This merges documentation read from a source file into the
4387 #                documentation read in from a template file.
4389 #                Parameter descriptions override any in the template files.
4390 #                Function descriptions are placed before any description from
4391 #                the template files.
4393 # Arguments   : none
4394 #############################################################################
4396 sub MergeSourceDocumentation {
4397     my $symbol;
4398     my @Symbols;
4400     if (scalar %SymbolDocs) {
4401         @Symbols=keys (%SymbolDocs);
4402         @TRACE@("num existing entries: ".(scalar @Symbols)."\n");
4403     }
4404     else {
4405         # filter scanned declarations, with what we suppress from -sections.txt
4406         my %tmp = ();
4407         foreach $symbol (keys (%Declarations)) {
4408             if (defined($KnownSymbols{$symbol}) && $KnownSymbols{$symbol} == 1) {
4409                 $tmp{$symbol}=1;
4410             }
4411         }
4412         # , add the rest from -sections.txt
4413         foreach $symbol (keys (%KnownSymbols)) {
4414             if ($KnownSymbols{$symbol} == 1) {
4415                 $tmp{$symbol}=1;
4416             }
4417         }
4418         # and add whats found in the source
4419         foreach $symbol (keys (%SourceSymbolDocs)) {
4420             $tmp{$symbol}=1;
4421         }
4422         @Symbols = keys (%tmp);
4423         @TRACE@("num source entries: ".(scalar @Symbols)."\n");
4424     }
4425     foreach $symbol (@Symbols) {
4426         $AllSymbols{$symbol} = 1;
4428         my $have_tmpl_docs = 0;
4430         ## see if the symbol is documented in template
4431         my $tmpl_doc = defined ($SymbolDocs{$symbol}) ? $SymbolDocs{$symbol} : "";
4432         my $check_tmpl_doc =$tmpl_doc;
4433         # remove all xml-tags and whitespaces
4434         $check_tmpl_doc =~ s/<.*?>//g;
4435         $check_tmpl_doc =~ s/\s//g;
4436         # anything left ?
4437         if ($check_tmpl_doc ne "") {
4438             $have_tmpl_docs = 1;
4439         } else {
4440             # if the docs have just an empty para, don't merge that.
4441             $check_tmpl_doc = $tmpl_doc;
4442             $check_tmpl_doc =~ s/(\s|\n)//msg;
4443             if ($check_tmpl_doc eq "<para></para>") {
4444                $tmpl_doc = "";
4445             }
4446         }
4448         if (exists ($SourceSymbolDocs{$symbol})) {
4449             my $type = $DeclarationTypes {$symbol};
4451             @TRACE@("merging [$symbol] from source\n");
4453             my $item = "Parameter";
4454             if (defined ($type)) {
4455                 if ($type eq 'STRUCT') {
4456                     $item = "Field";
4457                 } elsif ($type eq 'ENUM') {
4458                     $item = "Value";
4459                 } elsif ($type eq 'UNION') {
4460                     $item = "Field";
4461                 }
4462             } else {
4463                 $type="SIGNAL";
4464             }
4466             my $src_doc = $SourceSymbolDocs{$symbol};
4467             # remove leading and training whitespaces
4468             $src_doc =~ s/^\s+//;
4469             $src_doc =~ s/\s+$//;
4471             # Don't output warnings for overridden titles as titles are
4472             # automatically generated in the -sections.txt file, and thus they
4473             # are often overridden.
4474             if ($have_tmpl_docs && $symbol !~ m/:Title$/) {
4475                 # check if content is different
4476                 if ($tmpl_doc ne $src_doc) {
4477                     #print "[$tmpl_doc] [$src_doc]\n";
4478                     &LogWarning ($SourceSymbolSourceFile{$symbol}, $SourceSymbolSourceLine{$symbol},
4479                         "Documentation in template ".$SymbolSourceFile{$symbol}.":".$SymbolSourceLine{$symbol}." for $symbol being overridden by inline comments.");
4480                 }
4481             }
4483             if ($src_doc ne "") {
4484                  $AllDocumentedSymbols{$symbol} = 1;
4485             }
4487             # Do not add <para> to nothing, it breaks missing docs checks.
4488             my $src_doc_para = "";
4489             if ($src_doc ne "") {
4490                 $src_doc_para = $src_doc;
4491             }
4493             if ($symbol =~ m/$TMPL_DIR\/.+:Long_Description/) {
4494                 $SymbolDocs{$symbol} = "$src_doc_para$tmpl_doc";
4495             } elsif ($symbol =~ m/$TMPL_DIR\/.+:.+/) {
4496                 # For the title/summary/see also section docs we don't want to
4497                 # add any <para> tags.
4498                 $SymbolDocs{$symbol} = "$src_doc"
4499             } else {
4500                 $SymbolDocs{$symbol} = "$src_doc_para$tmpl_doc";
4501             }
4503             # merge parameters
4504             if ($symbol =~ m/.*::.*/) {
4505                 # For signals we prefer the param names from the source docs,
4506                 # since the ones from the templates are likely to contain the
4507                 # artificial argn names which are generated by gtkdoc-scangobj.
4508                 $SymbolParams{$symbol} = $SourceSymbolParams{$symbol};
4509                 # FIXME: we need to check for empty docs here as well!
4510             } else {
4511                 # The templates contain the definitive parameter names and order,
4512                 # so we will not change that. We only override the actual text.
4513                 my $tmpl_params = $SymbolParams{$symbol};
4514                 if (!defined ($tmpl_params)) {
4515                     @TRACE@("No merge needed for $symbol\n");
4516                     $SymbolParams{$symbol} = $SourceSymbolParams{$symbol};
4517                     #  FIXME: we still like to get the number of params and merge
4518                     #  1) we would noticed that params have been removed/renamed
4519                     #  2) we would catch undocumented params
4520                     #  params are not (yet) exported in -decl.txt so that we
4521                     #  could easily grab them :/
4522                 } else {
4523                     my $params = $SourceSymbolParams{$symbol};
4524                     my $j;
4525                     @TRACE@("Merge needed for $symbol, tmpl_params: ",$#$tmpl_params,", source_params: ",$#$params," \n");
4526                     for ($j = 0; $j <= $#$tmpl_params; $j += $PARAM_FIELD_COUNT) {
4527                         my $tmpl_param_name = $$tmpl_params[$j];
4529                         # Try to find the param in the source comment documentation.
4530                         my $found = 0;
4531                         my $k;
4532                         @TRACE@("  try merge param $tmpl_param_name\n");
4533                         for ($k = 0; $k <= $#$params; $k += $PARAM_FIELD_COUNT) {
4534                             my $param_name = $$params[$k];
4535                             my $param_desc = $$params[$k + 1];
4537                             @TRACE@("    test param  $param_name\n");
4538                             # We accept changes in case, since the Gnome source
4539                             # docs contain a lot of these.
4540                             if ("\L$param_name" eq "\L$tmpl_param_name") {
4541                                 $found = 1;
4543                                 # Override the description.
4544                                 $$tmpl_params[$j + 1] = $param_desc;
4546                                 # Set the name to "" to mark it as used.
4547                                 $$params[$k] = "";
4548                                 last;
4549                             }
4550                         }
4552                         # If it looks like the parameters are there, but not
4553                         # in the right place, try to explain a bit better.
4554                         if ((!$found) && ($src_doc =~ m/\@$tmpl_param_name:/)) {
4555                             &LogWarning (&GetSymbolSourceFile ($symbol), &GetSymbolSourceLine($symbol),
4556                                 "Parameters for $symbol must start on the line immediately after the function or macro name.");
4557                         }
4558                     }
4560                     # Now we output a warning if parameters have been described which
4561                     # do not exist.
4562                     for ($j = 0; $j <= $#$params; $j += $PARAM_FIELD_COUNT) {
4563                         my $param_name = $$params[$j];
4564                         if ($param_name) {
4565                             # the template builder cannot detect if a macro returns
4566                             # a result or not
4567                             if(($type eq "MACRO") && ($param_name eq "Returns")) {
4568                                 # FIXME: do we need to add it then to tmpl_params[] ?
4569                                 my $num=$#$tmpl_params;
4570                                 @TRACE@("  adding Returns: to macro docs for $symbol.\n");
4571                                 $$tmpl_params[$num+1]="Returns";
4572                                 $$tmpl_params[$num+2]=$$params[$j+1];
4573                                 next;
4574                             }
4575                             &LogWarning (&GetSymbolSourceFile ($symbol), &GetSymbolSourceLine($symbol),
4576                                 "$item described in source code comment block but does not exist. $type: $symbol $item: $param_name.");
4577                         }
4578                     }
4579                 }
4580             }
4581         } else {
4582             if ($have_tmpl_docs) {
4583                 $AllDocumentedSymbols{$symbol} = 1;
4584                 @TRACE@("merging [$symbol] from template\n");
4585             }
4586             else {
4587                 @TRACE@("[$symbol] undocumented\n");
4588             }
4589         }
4591         # if this symbol is documented, check if docs are complete
4592         $check_tmpl_doc = defined ($SymbolDocs{$symbol}) ? $SymbolDocs{$symbol} : "";
4593         # remove all xml-tags and whitespaces
4594         $check_tmpl_doc =~ s/<.*?>//g;
4595         $check_tmpl_doc =~ s/\s//g;
4596         if ($check_tmpl_doc ne "") {
4597             my $tmpl_params = $SymbolParams{$symbol};
4598             if (defined ($tmpl_params)) {
4599                 my $type = $DeclarationTypes {$symbol};
4601                 my $item = "Parameter";
4602                 if (defined ($type)) {
4603                     if ($type eq 'STRUCT') {
4604                         $item = "Field";
4605                     } elsif ($type eq 'ENUM') {
4606                         $item = "Value";
4607                     } elsif ($type eq 'UNION') {
4608                         $item = "Field";
4609                     }
4610                 } else {
4611                     $type="SIGNAL";
4612                 }
4614                 @TRACE@("Check param docs for $symbol, tmpl_params: ",$#$tmpl_params," entries, type=$type\n");
4616                 if ($#$tmpl_params > 0) {
4617                     my $j;
4618                     for ($j = 0; $j <= $#$tmpl_params; $j += $PARAM_FIELD_COUNT) {
4619                         # Output a warning if the parameter is empty and
4620                         # remember for stats.
4621                         my $tmpl_param_name = $$tmpl_params[$j];
4622                         my $tmpl_param_desc = $$tmpl_params[$j + 1];
4623                         if ($tmpl_param_name ne "void" && $tmpl_param_desc !~ m/\S/) {
4624                             if (exists ($AllIncompleteSymbols{$symbol})) {
4625                                 $AllIncompleteSymbols{$symbol}.=", ".$tmpl_param_name;
4626                             } else {
4627                                 $AllIncompleteSymbols{$symbol}=$tmpl_param_name;
4628                             }
4629                             &LogWarning (&GetSymbolSourceFile ($symbol), &GetSymbolSourceLine($symbol),
4630                                 "$item description for $symbol"."::"."$tmpl_param_name is missing in source code comment block.");
4631                         }
4632                     }
4633                 }
4634                 else {
4635                     if ($#$tmpl_params == 0) {
4636                         $AllIncompleteSymbols{$symbol}="<items>";
4637                         &LogWarning (&GetSymbolSourceFile ($symbol), &GetSymbolSourceLine($symbol),
4638                             "$item descriptions for $symbol are missing in source code comment block.");
4639                     }
4640                     # $#$tmpl_params==-1 means we don't know about parameters
4641                     # this unfortunately does not tell if there should be some
4642                 }
4643             }
4644         }
4645    }
4646    @TRACE@("num doc entries: ".(scalar %SymbolDocs)."\n");
4649 #############################################################################
4650 # Function    : IsEmptyDoc
4651 # Description : Check if a doc-string is empty. Its also regarded as empty if
4652 #               it only consist of whitespace or e.g. FIXME.
4653 # Arguments   : the doc-string
4654 #############################################################################
4655 sub IsEmptyDoc {
4656     my ($doc) = @_;
4658     if ($doc =~ /^\s*$/) {
4659         return 1;
4660     }
4662     if ($doc =~ /^\s*<para>\s*(FIXME)?\s*<\/para>\s*$/) {
4663         return 1;
4664     }
4666     return 0;
4669 #############################################################################
4670 # Function    : ConvertMarkDown
4671 # Description : Converts mark down syntax to the respective docbook.
4672 #               http://de.wikipedia.org/wiki/Markdown
4673 #               Inspired by the design of ParseDown
4674 #               http://parsedown.org/
4675 #               Copyright (c) 2013 Emanuil Rusev, erusev.com
4676 # Arguments   : the symbol name, the doc-string
4677 #############################################################################
4679 sub ConvertMarkDown {
4680     my ($symbol, $text) = @_;
4682     $text = &MarkDownParse ($text, $symbol);
4684     return $text
4687 # SUPPORTED MARKDOWN
4688 # ==================
4690 # Atx-style Headers
4691 # -----------------
4693 # # Header 1
4695 # ## Header 2 ##
4697 # Setext-style Headers
4698 # --------------------
4700 # Header 1
4701 # ========
4703 # Header 2
4704 # --------
4706 # Ordered (unnested) Lists
4707 # ------------------------
4709 # 1. item 1
4711 # 1. item 2 with loooong
4712 #    description
4714 # 3. item 3
4716 # Note: we require a blank line above the list items
4719 # TODO(ensonic): it would be nice to add id parameters to the refsect2 elements
4721 sub MarkDownParseBlocks {
4722   my ($linesref, $symbol, $context) = @_;
4723   my $line;
4724   my @md_blocks = ();
4725   my $md_block = { type => "" };
4727  OUTER: foreach $line (@$linesref) {
4728     my $first_char = substr ($line, 0, 1);
4729     my $deindented_line;
4731     if ($md_block->{"type"} eq "markup") {
4732       if (!$md_block->{"closed"}) {
4733         if (index ($line, $md_block->{"start"}) != -1) {
4734           $md_block->{"depth"}++;
4735         }
4736         if (index ($line, $md_block->{"end"}) != -1) {
4737           if ($md_block->{"depth"} > 0) {
4738             $md_block->{"depth"}--;
4739           } else {
4740             $md_block->{"closed"} = 1;
4741           }
4742         }
4743         $md_block->{"text"} .= "\n" . $line;
4744         next OUTER;
4745       }
4746     }
4748     $deindented_line = $line;
4749     $deindented_line =~ s/^\s+//;
4751     if ($md_block->{"type"} eq "heading") {
4752       # a heading is ended by any level less than or equal
4753       if ($md_block->{"level"} == 1) {
4754         if ($line =~ /^={4,}[ \t]*$/) {
4755           my $text = pop @{$md_block->{"lines"}};
4756           $md_block->{"interrupted"} = 0;
4757           push @md_blocks, $md_block;
4759           $md_block = { type => "heading",
4760                         text => $text,
4761                         lines => [],
4762                         level => 1 };
4763           next OUTER;
4764         } elsif ($line =~ /^[#][ \t]+(.+?)[ \t]*[#]*[ \t]*(?:{#([^}]+)})?[ \t]*$/) {
4765           $md_block->{"interrupted"} = 0;
4766           push @md_blocks, $md_block;
4768           $md_block = { type => "heading",
4769                         text => $1,
4770                         id => $2,
4771                         lines => [],
4772                         level => 1 };
4773           next OUTER;
4774         } else {
4775           # push lines into the block until the end is reached
4776           push @{$md_block->{"lines"}}, $line;
4777           next OUTER;
4778         }
4779       } else {
4780         if ($line =~ /^[=]{4,}[ \t]*$/) {
4781           my $text = pop @{$md_block->{"lines"}};
4782           $md_block->{"interrupted"} = 0;
4783           push @md_blocks, $md_block;
4785           $md_block = { type => "heading",
4786                         text => $text,
4787                         lines => [],
4788                         level => 1 };
4789           next OUTER;
4790         } elsif ($line =~ /^[-]{4,}[ \t]*$/) {
4791           my $text = pop @{$md_block->{"lines"}};
4792           $md_block->{"interrupted"} = 0;
4793           push @md_blocks, $md_block;
4795           $md_block = { type => "heading",
4796                         text => $text,
4797                         lines => [],
4798                         level => 2 };
4799           next OUTER;
4800         } elsif ($line =~ /^([#]{1,2})[ \t]+(.+?)[ \t]*[#]*[ \t]*(?:{#([^}]+)})?[ \t]*$/) {
4801           $md_block->{"interrupted"} = 0;
4802           push @md_blocks, $md_block;
4804           $md_block = { type => "heading",
4805                         text => $2,
4806                         id => $3,
4807                         lines => [],
4808                         level => length($1) };
4809           next OUTER;
4810         } else {
4811           # push lines into the block until the end is reached
4812           push @{$md_block->{"lines"}}, $line;
4813           next OUTER;
4814         }
4815       }
4816     } elsif ($md_block->{"type"} eq "code") {
4817       if ($line =~ /^[ \t]*\]\|/) {
4818         push @md_blocks, $md_block;
4819         $md_block = { type => "paragraph",
4820                       text => "",
4821                       lines => [] };
4822       } else {
4823         push @{$md_block->{"lines"}}, $line;
4824       }
4825       next OUTER;
4826     }
4828     if ($deindented_line eq "") {
4829       $md_block->{"interrupted"} = 1;
4830       next;
4831     }
4833     if ($md_block->{"type"} eq "quote") {
4834       if (!$md_block->{"interrupted"}) {
4835         $line =~ s/^[ ]*>[ ]?//;
4836         push @{$md_block->{"lines"}}, $line;
4837         next OUTER;
4838       }
4839     } elsif ($md_block->{"type"} eq "li") {
4840       my $marker = $md_block->{"marker"};
4841       if ($line =~ /^([ ]{0,3})($marker)[ ](.*)/) {
4842         my $indentation = $1;
4843         if ($md_block->{"indentation"} ne $indentation) {
4844           push @{$md_block->{"lines"}}, $line;
4845         } else {
4846           my $lines = $3;
4847           my $ordered = $md_block->{"ordered"};
4848           $lines =~ s/^[ ]{0,4}//;
4849           $md_block->{"last"} = 0;
4850           push @md_blocks, $md_block;
4851           $md_block = { type => "li",
4852                         ordered => $ordered,
4853                         indentation => $indentation,
4854                         marker => $marker,
4855                         first => 0,
4856                         last => 1,
4857                         lines => [ $lines ] };
4858         }
4859         next OUTER;
4860       }
4862       if ($md_block->{"interrupted"}) {
4863         if ($first_char eq " ") {
4864           push @{$md_block->{"lines"}}, "";
4865           $line =~ s/^[ ]{0,4}//;
4866           push @{$md_block->{"lines"}}, $line;
4867           $md_block->{"interrupted"} = 0;
4868           next OUTER;
4869         }
4870       } else {
4871         $line =~ s/^[ ]{0,4}//;
4872         push @{$md_block->{"lines"}}, $line;
4873         next OUTER;
4874       }
4875     }
4877     # indentation sensitive types
4879     if ($line =~ /^([#]{1,2})[ \t]+(.+?)[ \t]*[#]*[ \t]*(?:{#([^}]+)})?[ \t]*$/) {
4880       # atx heading (#)
4881       push @md_blocks, $md_block;
4883       $md_block = { type => "heading",
4884                     text => $2,
4885                     id => $3,
4886                     lines => [],
4887                     level => length($1) };
4889       next OUTER;
4890     } elsif ($line =~ /^={4,}[ \t]*$/) {
4891       # setext heading (====)
4893       if ($md_block->{"type"} eq "paragraph" && $md_block->{"interrupted"}) {
4894         push @md_blocks, $md_block;
4895         $md_block->{"type"} = "heading";
4896         $md_block->{"lines"} = [];
4897         $md_block->{"level"} = 1;
4898       }
4900       next OUTER;
4901     } elsif ($line =~ /^-{4,}[ \t]*$/) {
4902       # setext heading (-----)
4904       if ($md_block->{"type"} eq "paragraph" && $md_block->{"interrupted"}) {
4905         push @md_blocks, $md_block;
4906         $md_block->{"type"} = "heading";
4907         $md_block->{"lines"} = [];
4908         $md_block->{"level"} = 2;
4909       }
4911       next OUTER;
4912     } elsif ($line =~ /^[ \t]*\|\[[ ]*(?:<!-- language="([^"]+?)" -->)?/) {
4913       # code
4914       $md_block->{"interrupted"} = 1;
4915       push @md_blocks, $md_block;
4916       $md_block = { type => "code",
4917                     language => $1,
4918                     lines => [] };
4919       next OUTER;
4920     }
4922     # indentation insensitive types
4923     if ($line =~ /^[ ]*<!DOCTYPE/) {
4924       push @md_blocks, $md_block;
4926       $md_block = { type   => "markup",
4927                     text   => $deindented_line,
4928                     start  => "<",
4929                     end    => ">",
4930                     closed => 0,
4931                     depth  => 0 };
4933     } elsif ($line =~ /^[ ]*<\??(\w+)[^>]*([\/\?])?[ \t]*>/) {
4934       # markup, including <?xml version="1.0"?>
4935       my $tag = $1;
4936       my $is_self_closing = defined($2);
4937       # FIXME: why do we need to skip https? here, if we generalize this to all
4938       # uri schemes we get parsing errors
4939       if (! $MD_TEXT_LEVEL_ELEMENTS{$tag} && $tag !~ /^https?/) {
4940         push @md_blocks, $md_block;
4942         if ($is_self_closing) {
4943           $md_block = { type => "self-closing tag",
4944                         text => $deindented_line };
4945           $is_self_closing = 0;
4946           next OUTER;
4947         }
4949         $md_block = { type   => "markup",
4950                       text   => $deindented_line,
4951                       start  => "<" . $tag . ">",
4952                       end    => "</" . $tag . ">",
4953                       closed => 0,
4954                       depth  => 0 };
4955         if ($deindented_line =~ /<\/$tag>/) {
4956           $md_block->{"closed"} = 1;
4957         }
4958         next OUTER;
4959       }
4960     } elsif ($line =~ /^([ ]*)[*+-][ ](.*)/) {
4961       # li
4962       push @md_blocks, $md_block;
4963       my $lines = $2;
4964       my $indentation = $1;
4965       $lines =~ s/^[ ]{0,4}//;
4966       $md_block = { type => "li",
4967                     ordered => 0,
4968                     indentation => $indentation,
4969                     marker => "[*+-]",
4970                     first => 1,
4971                     last => 1,
4972                     lines => [ $lines ] };
4973       next OUTER;
4974     } elsif ($line =~ /^[ ]*>[ ]?(.*)/) {
4975       push @md_blocks, $md_block;
4976       $md_block = { type => "quote",
4977                     lines => [ $1 ] };
4978       next OUTER;
4979     }
4981     # list item
4983     if ($line =~ /^([ ]{0,4})\d+[.][ ]+(.*)/) {
4984       push @md_blocks, $md_block;
4985       my $lines = $2;
4986       my $indentation = $1;
4987       $lines =~ s/^[ ]{0,4}//;
4989       $md_block = { type => "li",
4990                     ordered => 1,
4991                     indentation => $indentation,
4992                     marker => "\\d+[.]",
4993                     first => 1,
4994                     last => 1,
4995                     lines => [ $lines ] };
4997       next;
4998     }
5000     # paragraph
5001     if ($md_block->{"type"} eq "paragraph") {
5002       if ($md_block->{"interrupted"}) {
5003         push @md_blocks, $md_block;
5004         $md_block = { type => "paragraph",
5005                       interrupted => 0,
5006                       text => $line };
5007       } else {
5008         $md_block->{"text"} .= "\n" . $line;
5009       }
5010     } else {
5011       push @md_blocks, $md_block;
5012       $md_block = { type => "paragraph",
5013                     text => $line };
5014     }
5015   }
5017   push @md_blocks, $md_block;
5019   shift @md_blocks;
5021   return @md_blocks;
5024 sub MarkDownParseSpanElementsInner {
5025   my ($text, $markersref) = @_;
5026   my $markup = "";
5027   my %markers = map { $_ => 1 } @$markersref;
5029   while ($text ne "") {
5030     my $closest_marker = "";
5031     my $closest_marker_index = 0;
5032     my $closest_marker_position = -1;
5033     my $text_marker = "";
5034     my $i = 0;
5035     my $offset = 0;
5036     my @markers_rest;
5037     my $marker;
5038     my $use;
5040     while ( ($marker, $use) = each %markers ) {
5041       my $marker_position;
5043       if (!$use) {
5044         next;
5045       }
5047       $marker_position = index ($text, $marker);
5049       if ($marker_position < 0) {
5050         $markers{$marker} = 0;
5051         next;
5052       }
5054       if ($closest_marker eq "" || $marker_position < $closest_marker_position) {
5055         $closest_marker = $marker;
5056         $closest_marker_index = $i;
5057         $closest_marker_position = $marker_position;
5058       }
5059     }
5061     if ($closest_marker_position >= 0) {
5062       $text_marker = substr ($text, $closest_marker_position);
5063     }
5065     if ($text_marker eq "") {
5066       $markup .= $text;
5067       $text = "";
5068       next; # last
5069     }
5071     $markup .= substr ($text, 0, $closest_marker_position);
5072     $text = substr ($text, $closest_marker_position);
5073     @markers_rest = map { $markers{$_} ? ($_ eq $closest_marker ? () : $_) : () } keys %markers;
5075     if ($closest_marker eq "![" || $closest_marker eq "[") {
5076       my %element;
5078       if (index ($text, "]") && $text =~ /\[((?:[^][]|(?R))*)\]/) {
5079         my $remaining_text;
5081         %element = ( "!" => (substr ($text, 0, 1) eq "!"),
5082                      "a" => $1 );
5084         $offset = length ($&);
5085         if ($element{"!"}) {
5086           $offset++;
5087         }
5089         $remaining_text = substr ($text, $offset);
5090         if ($remaining_text =~ /^\([ ]*([^)'"]*?)(?:[ ]+['"](.+?)['"])?[ ]*\)/) {
5091           $element{"»"} = $1;
5092           if (defined ($2)) {
5093             $element{"#"} = $2;
5094           }
5095           $offset += length ($&);
5096         } elsif ($remaining_text =~ /^\s*\[([^\]<]*?)\]/) {
5097           $element{"ref"} = $1;
5098           $offset += length ($&);
5099         } else {
5100           undef %element;
5101         }
5102       }
5104       if (%element) {
5105         if ($element{"»"}) {
5106           $element{"»"} =~ s/&/&amp;/g;
5107           $element{"»"} =~ s/</&lt;/g;
5108         }
5109         if ($element{"!"}) {
5110           $markup .= "<inlinemediaobject><imageobject><imagedata fileref=\"" . $element{"»"} . "\"></imagedata></imageobject>";
5112           if (defined ($element{"a"})) {
5113             $markup .= "<textobject><phrase>" . $element{"a"} . "</phrase></textobject>";
5114           }
5116           $markup .= "</inlinemediaobject>";
5117         } elsif ($element{"ref"}) {
5118           $element{"a"} = &MarkDownParseSpanElementsInner ($element{"a"}, \@markers_rest);
5119           $markup .= "<link linkend=\"" . $element{"ref"} . "\"";
5121           if (defined ($element{"#"})) {
5122             # title attribute not supported
5123           }
5125           $markup .= ">" . $element{"a"} . "</link>";
5126         } else {
5127           $element{"a"} = &MarkDownParseSpanElementsInner ($element{"a"}, \@markers_rest);
5128           $markup .= "<ulink url=\"" . $element{"»"} . "\"";
5130           if (defined ($element{"#"})) {
5131             # title attribute not supported
5132           }
5134           $markup .= ">" . $element{"a"} . "</ulink>";
5135         }
5136       } else {
5137         $markup .= $closest_marker;
5138         if ($closest_marker eq "![") {
5139           $offset = 2;
5140         } else {
5141           $offset = 1;
5142         }
5143       }
5144     } elsif ($closest_marker eq "<") {
5145       if ($text =~ /^<(https?:[\/]{2}[^\s]+?)>/i) {
5146         my $element_url = $1;
5147         $element_url =~ s/&/&amp;/g;
5148         $element_url =~ s/</&lt;/g;
5150         $markup .= "<ulink url=\"" . $element_url . "\">" . $element_url . "</ulink>";
5151         $offset = length ($&);
5152       } elsif ($text =~ /^<([A-Za-z0-9._-]+?@[A-Za-z0-9._-]+?)>/) {
5153         $markup .= "<ulink url=\"mailto:" . $1 . "\">" . $1 . "</ulink>";
5154         $offset = length ($&);
5155       } elsif ($text =~ /^<[^>]+?>/) {
5156         $markup .= $&;
5157         $offset = length ($&);
5158       } else {
5159         $markup .= "&lt;";
5160         $offset = 1;
5161       }
5162     } elsif ($closest_marker eq "\\") {
5163       my $special_char = substr ($text, 1, 1);
5164       if ($MD_ESCAPABLE_CHARS{$special_char} ||
5165           $MD_GTK_ESCAPABLE_CHARS{$special_char}) {
5166         $markup .= $special_char;
5167         $offset = 2;
5168       } else {
5169         $markup .= "\\";
5170         $offset = 1;
5171       }
5172     } elsif ($closest_marker eq "`") {
5173       if ($text =~ /^(`+)([^`]+?)\1(?!`)/) {
5174         my $element_text = $2;
5175         $markup .= "<literal>" . $element_text . "</literal>";
5176         $offset = length ($&);
5177       } else {
5178         $markup .= "`";
5179         $offset = 1;
5180       }
5181     } elsif ($closest_marker eq "@") {
5182       # Convert '@param()'
5183       # FIXME: we could make those also links ($symbol.$2), but that would be less
5184       # useful as the link target is a few lines up or down
5185       if ($text =~ /^(\A|[^\\])\@(\w+((\.|->)\w+)*)\s*\(\)/) {
5186         $markup .= $1 . "<parameter>" . $2 . "()</parameter>\n";
5187         $offset = length ($&);
5188       } elsif ($text =~ /^(\A|[^\\])\@(\w+((\.|->)\w+)*)/) {
5189         # Convert '@param', but not '\@param'.
5190         $markup .= $1 . "<parameter>" . $2 . "</parameter>\n";
5191         $offset = length ($&);
5192       } elsif ($text =~ /^\\\@/) {
5193         $markup .= "\@";
5194         $offset = length ($&);
5195       } else {
5196         $markup .= "@";
5197         $offset = 1;
5198       }
5199     } elsif ($closest_marker eq "#") {
5200       if ($text =~ /^(\A|[^\\])#([\w\-:\.]+[\w]+)\s*\(\)/) {
5201         # handle #Object.func()
5202         $markup .= $1 . &MakeXRef ($2, &tagify ($2 . "()", "function"));
5203         $offset = length ($&);
5204       } elsif ($text =~ /^(\A|[^\\])#([\w\-:\.]+[\w]+)/) {
5205         # Convert '#symbol', but not '\#symbol'.
5206         $markup .= $1 . &MakeHashXRef ($2, "type");
5207         $offset = length ($&);
5208       } elsif ($text =~ /^\\#/) {
5209         $markup .= "#";
5210         $offset = length ($&);
5211       } else {
5212         $markup .= "#";
5213         $offset = 1;
5214       }
5215     } elsif ($closest_marker eq "%") {
5216       if ($text =~ /^(\A|[^\\])\%(-?\w+)/) {
5217         # Convert '%constant', but not '\%constant'.
5218         # Also allow negative numbers, e.g. %-1.
5219         $markup .= $1 . &MakeXRef ($2, &tagify ($2, "literal"));
5220         $offset = length ($&);
5221       } elsif ($text =~ /^\\%/) {
5222         $markup .= "\%";
5223         $offset = length ($&);
5224       } else {
5225         $markup .= "%";
5226         $offset = 1;
5227       }
5228     }
5230     if ($offset > 0) {
5231       $text = substr ($text, $offset);
5232     }
5233   }
5235   return $markup;
5238 sub MarkDownParseSpanElements {
5239   my ($text) = @_;
5240   my @markers = ( "\\", "<", "![", "[", "`", "%", "#", "@" );
5242   $text = &MarkDownParseSpanElementsInner ($text, \@markers);
5244   # Convert 'function()' or 'macro()'.
5245   # if there is abc_*_def() we don't want to make a link to _def()
5246   # FIXME: also handle abc(def(....)) : but that would need to be done recursively :/
5247   $text =~ s/([^\*.\w])(\w+)\s*\(\)/$1.&MakeXRef($2, &tagify($2 . "()", "function"));/eg;
5249   return $text;
5252 sub ReplaceEntities {
5253   my ($text, $symbol) = @_;
5254   my $warn = "";
5255   my @entities = ( [ "&lt;", "<" ],
5256                    [ "&gt;", ">" ],
5257                    [ "&ast;", "*" ],
5258                    [ "&num;", "#" ],
5259                    [ "&percnt;", "%"],
5260                    [ "&colon;", ":" ],
5261                    [ "&quot;", "\"" ],
5262                    [ "&apos;", "'" ],
5263                    [ "&nbsp;", " " ],
5264                    [ "&amp;", "&" ] ); # Do this last, or the others get messed up.
5265   my $i;
5267   # Expand entities in <programlisting> even inside CDATA since
5268   # we changed the definition of |[ to add CDATA
5269   for ($i = 0; $i <= $#entities; $i++) {
5270     $text =~ s/$entities[$i][0]/$entities[$i][1]/g;
5271   }
5273   return $text;
5276 sub MarkDownOutputDocBook {
5277   my ($blocksref, $symbol, $context) = @_;
5278   my $output = "";
5279   my $block;
5280   my @blocks = @$blocksref;
5282   foreach $block (@blocks) {
5283     my $text;
5284     my $title;
5286     if ($block->{"type"} eq "paragraph") {
5287       $text = &MarkDownParseSpanElements ($block->{"text"});
5288       if ($context eq "li" && $output eq "") {
5289         if ($block->{"interrupted"}) {
5290           $output .= "\n"."<para>".$text."</para>"."\n";
5291         } else {
5292           $output .= "<para>".$text."</para>";
5293           if ($#blocks > 0) {
5294             $output .= "\n";
5295           }
5296         }
5297       } else {
5298         $output .= "<para>".$text."</para>"."\n";
5299       }
5301     } elsif ($block->{"type"} eq "heading") {
5302       my $tag;
5304       $title = &MarkDownParseSpanElements ($block->{"text"});
5306       if ($block->{"level"} == 1) {
5307         $tag = "refsect2";
5308       } else {
5309         $tag = "refsect3";
5310       }
5312       $text = &MarkDownParseLines ($block->{"lines"}, $symbol, "heading");
5313       if (defined ($block->{"id"})) {
5314         $output .= "<" . $tag . " id=\"" . $block->{"id"} . "\">";
5315       } else {
5316         $output .= "<" . $tag . ">";
5317       }
5319       $output .= "<title>" . $title . "</title>" . $text . "</" . $tag . ">\n";
5320     } elsif ($block->{"type"} eq "li") {
5321       my $tag = "itemizedlist";
5323       if ($block->{"first"}) {
5324         if ($block->{"ordered"}) {
5325           $tag = "orderedlist";
5326         }
5327         $output .= "<".$tag.">\n";
5328       }
5330       if ($block->{"interrupted"}) {
5331         push @{$block->{"lines"}}, "";
5332       }
5334       $text = &MarkDownParseLines ($block->{"lines"}, $symbol, "li");
5335       $output .= "<listitem>".$text."</listitem>\n";
5336       if ($block->{"last"}) {
5337         if ($block->{"ordered"}) {
5338           $tag = "orderedlist";
5339         }
5340         $output .= "</".$tag.">\n";
5341       }
5342     } elsif ($block->{"type"} eq "quote") {
5343       $text = &MarkDownParseLines ($block->{"lines"}, $symbol, "quote");
5344       $output .= "<blockquote>\n" . $text . "</blockquote>\n";
5345     } elsif ($block->{"type"} eq "code") {
5346       if ($block->{"language"}) {
5347         $output .= "<informalexample><programlisting language=\"" . $block->{"language"} . "\"><![CDATA[\n";
5348       } else {
5349         $output .= "<informalexample><programlisting><![CDATA[\n";
5350       }
5351       foreach (@{$block->{"lines"}}) {
5352         $output .= &ReplaceEntities ($_, $symbol) . "\n";
5353       }
5354       $output .= "]]></programlisting></informalexample>\n";
5355     } elsif ($block->{"type"} eq "markup") {
5356       $text = &ExpandAbbreviations($symbol, $block->{"text"});
5357       $output .= $text."\n";
5358     } else {
5359       $output .= $block->{"text"}."\n";
5360     }
5361   }
5363   return $output;
5366 sub MarkDownParseLines {
5367   my ($linesref, $symbol, $context) = @_;
5368   my $output;
5369   my @lines = @$linesref;
5370   my @blocks;
5372   @blocks = &MarkDownParseBlocks (\@lines, $symbol, $context);
5373   $output = &MarkDownOutputDocBook (\@blocks, $symbol, $context);
5375   return $output;
5378 sub MarkDownParse {
5379   my ($text, $symbol) = @_;
5380   my @lines;
5382   # take out some variability in line endings
5383   $text =~ s%\r\n%\n%g;
5384   $text =~ s%\r%\n%g;
5386   # split lines
5387   @lines = split("\n", $text);
5388   $text = MarkDownParseLines(\@lines, $symbol, "");
5390   return $text;
5393 #############################################################################
5394 # LIBRARY FUNCTIONS -        These functions are used in both gtkdoc-mkdb and
5395 #                        gtkdoc-mktmpl and should eventually be moved to a
5396 #                        separate library.
5397 #############################################################################
5399 #############################################################################
5400 # Function    : ReadDeclarationsFile
5401 # Description : This reads in a file containing the function/macro/enum etc.
5402 #                declarations.
5404 #                Note that in some cases there are several declarations with
5405 #                the same name, e.g. for conditional macros. In this case we
5406 #                set a flag in the %DeclarationConditional hash so the
5407 #                declaration is not shown in the docs.
5409 #                If a macro and a function have the same name, e.g. for
5410 #                gtk_object_ref, the function declaration takes precedence.
5412 #                Some opaque structs are just declared with 'typedef struct
5413 #                _name name;' in which case the declaration may be empty.
5414 #                The structure may have been found later in the header, so
5415 #                that overrides the empty declaration.
5417 # Arguments   : $file - the declarations file to read
5418 #                $override - if declarations in this file should override
5419 #                        any current declaration.
5420 #############################################################################
5422 sub ReadDeclarationsFile {
5423     my ($file, $override) = @_;
5425     if ($override == 0) {
5426         %Declarations = ();
5427         %DeclarationTypes = ();
5428         %DeclarationConditional = ();
5429         %DeclarationOutput = ();
5430     }
5432     open (INPUT, $file)
5433         || die "Can't open $file: $!";
5434     my $declaration_type = "";
5435     my $declaration_name;
5436     my $declaration;
5437     my $is_deprecated = 0;
5438     while (<INPUT>) {
5439         if (!$declaration_type) {
5440             if (m/^<([^>]+)>/) {
5441                 $declaration_type = $1;
5442                 $declaration_name = "";
5443                 @TRACE@("Found declaration: $declaration_type\n");
5444                 $declaration = "";
5445             }
5446         } else {
5447             if (m%^<NAME>(.*)</NAME>%) {
5448                 $declaration_name = $1;
5449             } elsif (m%^<DEPRECATED/>%) {
5450                 $is_deprecated = 1;
5451             } elsif (m%^</$declaration_type>%) {
5452                 @TRACE@("Found end of declaration: $declaration_name\n");
5453                 # Check that the declaration has a name
5454                 if ($declaration_name eq "") {
5455                     &LogWarning ($file, $., "$declaration_type has no name.\n");
5456                 }
5458                 # If the declaration is an empty typedef struct _XXX XXX
5459                 # set the flag to indicate the struct has a typedef.
5460                 if (($declaration_type eq 'STRUCT' || $declaration_type eq 'UNION')
5461                     && $declaration =~ m/^\s*$/) {
5462                     @TRACE@("Struct has typedef: $declaration_name\n");
5463                     $StructHasTypedef{$declaration_name} = 1;
5464                 }
5466                 # Check if the symbol is already defined.
5467                 if (defined ($Declarations{$declaration_name})
5468                     && $override == 0) {
5469                     # Function declarations take precedence.
5470                     if ($DeclarationTypes{$declaration_name} eq 'FUNCTION') {
5471                         # Ignore it.
5472                     } elsif ($declaration_type eq 'FUNCTION') {
5473                         if ($is_deprecated) {
5474                             $Deprecated{$declaration_name} = "";
5475                         }
5476                         $Declarations{$declaration_name} = $declaration;
5477                         $DeclarationTypes{$declaration_name} = $declaration_type;
5478                     } elsif ($DeclarationTypes{$declaration_name}
5479                               eq $declaration_type) {
5480                         # If the existing declaration is empty, or is just a
5481                         # forward declaration of a struct, override it.
5482                         if ($declaration_type eq 'STRUCT' || $declaration_type eq 'UNION') {
5483                             if ($Declarations{$declaration_name} =~ m/^\s*((struct|union)\s+\w+\s*;)?\s*$/) {
5484                                 if ($is_deprecated) {
5485                                     $Deprecated{$declaration_name} = "";
5486                                 }
5487                                 $Declarations{$declaration_name} = $declaration;
5488                             } elsif ($declaration =~ m/^\s*((struct|union)\s+\w+\s*;)?\s*$/) {
5489                                 # Ignore an empty or forward declaration.
5490                             } else {
5491                                 &LogWarning ($file, $., "Structure $declaration_name has multiple definitions.");
5492                             }
5493                         } else {
5494                             # set flag in %DeclarationConditional hash for
5495                             # multiply defined macros/typedefs.
5496                             $DeclarationConditional{$declaration_name} = 1;
5497                         }
5498                     } else {
5499                         &LogWarning ($file, $., "$declaration_name has multiple definitions.");
5500                     }
5501                 } else {
5502                     if ($is_deprecated) {
5503                         $Deprecated{$declaration_name} = "";
5504                     }
5505                     $Declarations{$declaration_name} = $declaration;
5506                     $DeclarationTypes{$declaration_name} = $declaration_type;
5507                 }
5509                 $declaration_type = "";
5510                 $is_deprecated = 0;
5511             } else {
5512                 $declaration .= $_;
5513             }
5514         }
5515     }
5516     close (INPUT);
5520 #############################################################################
5521 # Function    : ReadSignalsFile
5522 # Description : This reads in an existing file which contains information on
5523 #                all GTK signals. It creates the arrays @SignalNames and
5524 #                @SignalPrototypes containing info on the signals. The first
5525 #                line of the SignalPrototype is the return type of the signal
5526 #                handler. The remaining lines are the parameters passed to it.
5527 #                The last parameter, "gpointer user_data" is always the same
5528 #                so is not included.
5529 # Arguments   : $file - the file containing the signal handler prototype
5530 #                        information.
5531 #############################################################################
5533 sub ReadSignalsFile {
5534     my ($file) = @_;
5536     my $in_signal = 0;
5537     my $signal_object;
5538     my $signal_name;
5539     my $signal_returns;
5540     my $signal_flags;
5541     my $signal_prototype;
5543     # Reset the signal info.
5544     @SignalObjects = ();
5545     @SignalNames = ();
5546     @SignalReturns = ();
5547     @SignalFlags = ();
5548     @SignalPrototypes = ();
5550     if (! -f $file) {
5551         return;
5552     }
5553     if (!open (INPUT, $file)) {
5554         warn "Can't open $file - skipping signals\n";
5555         return;
5556     }
5557     while (<INPUT>) {
5558         if (!$in_signal) {
5559             if (m/^<SIGNAL>/) {
5560                 $in_signal = 1;
5561                 $signal_object = "";
5562                 $signal_name = "";
5563                 $signal_returns = "";
5564                 $signal_prototype = "";
5565             }
5566         } else {
5567             if (m/^<NAME>(.*)<\/NAME>/) {
5568                 $signal_name = $1;
5569                 if ($signal_name =~ m/^(.*)::(.*)$/) {
5570                     $signal_object = $1;
5571                     ($signal_name = $2) =~ s/_/-/g;
5572                     @TRACE@("Found signal: $signal_name\n");
5573                 } else {
5574                     &LogWarning ($file, $., "Invalid signal name: $signal_name.");
5575                 }
5576             } elsif (m/^<RETURNS>(.*)<\/RETURNS>/) {
5577                 $signal_returns = $1;
5578             } elsif (m/^<FLAGS>(.*)<\/FLAGS>/) {
5579                 $signal_flags = $1;
5580             } elsif (m%^</SIGNAL>%) {
5581                 @TRACE@("Found end of signal: ${signal_object}::${signal_name}\nReturns: ${signal_returns}\n${signal_prototype}");
5582                 push (@SignalObjects, $signal_object);
5583                 push (@SignalNames, $signal_name);
5584                 push (@SignalReturns, $signal_returns);
5585                 push (@SignalFlags, $signal_flags);
5586                 push (@SignalPrototypes, $signal_prototype);
5587                 $in_signal = 0;
5588             } else {
5589                 $signal_prototype .= $_;
5590             }
5591         }
5592     }
5593     close (INPUT);
5597 #############################################################################
5598 # Function    : ReadTemplateFile
5599 # Description : This reads in the manually-edited documentation file
5600 #               corresponding to the file currently being created, so we can
5601 #               insert the documentation at the appropriate places.
5602 #               It outputs %SymbolTypes, %SymbolDocs and %SymbolParams, which
5603 #               is a hash of arrays.
5604 #               NOTE: This function is duplicated in gtkdoc-mktmpl (but
5605 #               slightly different).
5606 # Arguments   : $docsfile - the template file to read in.
5607 #               $skip_unused_params - 1 if the unused parameters should be
5608 #                 skipped.
5609 #############################################################################
5611 sub ReadTemplateFile {
5612     my ($docsfile, $skip_unused_params) = @_;
5614     my $template = "$docsfile.sgml";
5615     if (! -f $template) {
5616         @TRACE@("File doesn't exist: $template\n");
5617         return 0;
5618     }
5620     # start with empty hashes, we merge the source comment for each file
5621     # afterwards
5622     %SymbolDocs = ();
5623     %SymbolTypes = ();
5624     %SymbolParams = ();
5626     my $current_type = "";        # Type of symbol being read.
5627     my $current_symbol = "";        # Name of symbol being read.
5628     my $symbol_doc = "";                # Description of symbol being read.
5629     my @params;                        # Parameter names and descriptions of current
5630                                 #   function/macro/function typedef.
5631     my $current_param = -1;        # Index of parameter currently being read.
5632                                 #   Note that the param array contains pairs
5633                                 #   of param name & description.
5634     my $in_unused_params = 0;        # True if we are reading in the unused params.
5635     my $in_deprecated = 0;
5636     my $in_since = 0;
5637     my $in_stability = 0;
5639     open (DOCS, "$template")
5640         || die "Can't open $template: $!";
5642     @TRACE@("reading template $template");
5644     while (<DOCS>) {
5645         if (m/^<!-- ##### ([A-Z_]+) (\S+) ##### -->/) {
5646             my $type = $1;
5647             my $symbol = $2;
5648             if ($symbol eq "Title"
5649                 || $symbol eq "Short_Description"
5650                 || $symbol eq "Long_Description"
5651                 || $symbol eq "See_Also"
5652                 || $symbol eq "Stability_Level"
5653                 || $symbol eq "Include"
5654                 || $symbol eq "Image") {
5656                 $symbol = $docsfile . ":" . $symbol;
5657             }
5659             @TRACE@("Found symbol: $symbol\n");
5660             # Remember file and line for the symbol
5661             $SymbolSourceFile{$symbol} = $template;
5662             $SymbolSourceLine{$symbol} = $.;
5664             # Store previous symbol, but remove any trailing blank lines.
5665             if ($current_symbol ne "") {
5666                 $symbol_doc =~ s/\s+$//;
5667                 $SymbolTypes{$current_symbol} = $current_type;
5668                 $SymbolDocs{$current_symbol} = $symbol_doc;
5670                 # Check that the stability level is valid.
5671                 if ($StabilityLevel{$current_symbol}) {
5672                     $StabilityLevel{$current_symbol} = &ParseStabilityLevel($StabilityLevel{$current_symbol}, $template, $., "Stability level for $current_symbol");
5673                 }
5675                 if ($current_param >= 0) {
5676                     $SymbolParams{$current_symbol} = [ @params ];
5677                 } else {
5678                     # Delete any existing params in case we are overriding a
5679                     # previously read template.
5680                     delete $SymbolParams{$current_symbol};
5681                 }
5682             }
5683             $current_type = $type;
5684             $current_symbol = $symbol;
5685             $current_param = -1;
5686             $in_unused_params = 0;
5687             $in_deprecated = 0;
5688             $in_since = 0;
5689             $in_stability = 0;
5690             $symbol_doc = "";
5691             @params = ();
5693         } elsif (m/^<!-- # Unused Parameters # -->/) {
5694             @TRACE@("Found unused parameters\n");
5695             $in_unused_params = 1;
5696             next;
5698         } elsif ($in_unused_params && $skip_unused_params) {
5699             # When outputting the DocBook we skip unused parameters.
5700             @TRACE@("Skipping unused param: $_");
5701             next;
5703         } else {
5704             # Check if param found. Need to handle "..." and "format...".
5705             if (s/^\@([\w\.]+):\040?//) {
5706                 my $param_name = $1;
5707                 my $param_desc = $_;
5708                 # Allow variations of 'Returns'
5709                 if ($param_name =~ m/^[Rr]eturns?$/) {
5710                     $param_name = "Returns";
5711                 }
5712                 # Allow varargs variations
5713                 if ($param_name =~ m/^.*\.\.\.$/) {
5714                     $param_name = "...";
5715                 }
5717                 # strip trailing whitespaces and blank lines
5718                 s/\s+\n$/\n/m;
5719                 s/\n+$/\n/sm;
5720                 @TRACE@("Found param for symbol $current_symbol : '$param_name'= '$_'");
5722                 if ($param_name eq "Deprecated") {
5723                     $in_deprecated = 1;
5724                     $Deprecated{$current_symbol} = $_;
5725                 } elsif ($param_name eq "Since") {
5726                     $in_since = 1;
5727                     chomp;
5728                     $Since{$current_symbol} = $_;
5729                 } elsif ($param_name eq "Stability") {
5730                     $in_stability = 1;
5731                     $StabilityLevel{$current_symbol} = $_;
5732                 } else {
5733                     push (@params, $param_name);
5734                     push (@params, $param_desc);
5735                     $current_param += $PARAM_FIELD_COUNT;
5736                 }
5737             } else {
5738                 # strip trailing whitespaces and blank lines
5739                 s/\s+\n$/\n/m;
5740                 s/\n+$/\n/sm;
5742                 if (!m/^\s+$/) {
5743                     if ($in_deprecated) {
5744                         $Deprecated{$current_symbol} .= $_;
5745                     } elsif ($in_since) {
5746                         &LogWarning ($template, $., "multi-line since docs found");
5747                         #$Since{$current_symbol} .= $_;
5748                     } elsif ($in_stability) {
5749                         $StabilityLevel{$current_symbol} .= $_;
5750                     } elsif ($current_param >= 0) {
5751                         $params[$current_param] .= $_;
5752                     } else {
5753                         $symbol_doc .= $_;
5754                     }
5755                 }
5756             }
5757         }
5758     }
5760     # Remember to finish the current symbol doccs.
5761     if ($current_symbol ne "") {
5763         $symbol_doc =~ s/\s+$//;
5764         $SymbolTypes{$current_symbol} = $current_type;
5765         $SymbolDocs{$current_symbol} = $symbol_doc;
5767         # Check that the stability level is valid.
5768         if ($StabilityLevel{$current_symbol}) {
5769             $StabilityLevel{$current_symbol} = &ParseStabilityLevel($StabilityLevel{$current_symbol}, $template, $., "Stability level for $current_symbol");
5770         }
5772         if ($current_param >= 0) {
5773             $SymbolParams{$current_symbol} = [ @params ];
5774         } else {
5775             # Delete any existing params in case we are overriding a
5776             # previously read template.
5777             delete $SymbolParams{$current_symbol};
5778         }
5779     }
5781     close (DOCS);
5782     return 1;
5786 #############################################################################
5787 # Function    : ReadObjectHierarchy
5788 # Description : This reads in the $MODULE-hierarchy.txt file containing all
5789 #               the GtkObject subclasses described in this module (and their
5790 #               ancestors).
5791 #               It places them in the @Objects array, and places their level
5792 #               in the object hierarchy in the @ObjectLevels array, at the
5793 #               same index. GtkObject, the root object, has a level of 1.
5795 #               This also generates tree_index.sgml as it goes along.
5797 # Arguments   : none
5798 #############################################################################
5800 sub ReadObjectHierarchy {
5801     @Objects = ();
5802     @ObjectLevels = ();
5804     if (! -f $OBJECT_TREE_FILE) {
5805         return;
5806     }
5807     if (!open (INPUT, $OBJECT_TREE_FILE)) {
5808         warn "Can't open $OBJECT_TREE_FILE - skipping object tree\n";
5809         return;
5810     }
5812     # Only emit objects if they are supposed to be documented, or if
5813     # they have documented children. To implement this, we maintain a
5814     # stack of pending objects which will be emitted if a documented
5815     # child turns up.
5816     my @pending_objects = ();
5817     my @pending_levels = ();
5818     my $root;
5819     my @tree = ();
5820     while (<INPUT>) {
5821         if (m/\S+/) {
5822             my $object = $&;
5823             my $level = (length($`)) / 2 + 1;
5824             my $xref = "";
5826             if ($level == 1) {
5827                 $root = $object;
5828             }
5830             while (($#pending_levels >= 0) && ($pending_levels[$#pending_levels] >= $level)) {
5831                 my $pobject = pop(@pending_objects);
5832                 my $plevel = pop(@pending_levels);
5833             }
5835             push (@pending_objects, $object);
5836             push (@pending_levels, $level);
5838             if (exists($KnownSymbols{$object})) {
5839                 while ($#pending_levels >= 0) {
5840                     $object = shift @pending_objects;
5841                     $level = shift @pending_levels;
5842                     $xref = &MakeXRef ($object);
5844                     push (@tree, ' ' x ($level * 4) . "$xref");
5845                     push (@Objects, $object);
5846                     push (@ObjectLevels, $level);
5847                     $ObjectRoots{$object} = $root;
5848                 }
5849             }
5850             #else {
5851             #    LogWarning ($OBJECT_TREE_FILE, $., "unknown type $object");
5852             #}
5853         }
5854     }
5855     close (INPUT);
5857     # FIXME: use $OUTPUT_FORMAT
5858     # my $old_tree_index = "$SGML_OUTPUT_DIR/tree_index.$OUTPUT_FORMAT";
5859     my $old_tree_index = "$SGML_OUTPUT_DIR/tree_index.sgml";
5860     my $new_tree_index = "$SGML_OUTPUT_DIR/tree_index.new";
5862     open (OUTPUT, ">$new_tree_index")
5863         || die "Can't create $new_tree_index: $!";
5865     if ($OUTPUT_FORMAT eq "xml") {
5866         my $tree_header = $doctype_header;
5868         $tree_header =~ s/<!DOCTYPE \w+/<!DOCTYPE screen/;
5869         print (OUTPUT "$tree_header");
5870     }
5871     print (OUTPUT "<screen>\n" . &AddTreeLineArt(\@tree) . "\n</screen>\n");
5872     close (OUTPUT);
5874     &UpdateFileIfChanged ($old_tree_index, $new_tree_index, 0);
5876     &OutputObjectList;
5879 #############################################################################
5880 # Function    : ReadInterfaces
5881 # Description : This reads in the $MODULE.interfaces file.
5883 # Arguments   : none
5884 #############################################################################
5886 sub ReadInterfaces {
5887     %Interfaces = ();
5889     if (! -f $INTERFACES_FILE) {
5890         return;
5891     }
5892     if (!open (INPUT, $INTERFACES_FILE)) {
5893         warn "Can't open $INTERFACES_FILE - skipping interfaces\n";
5894         return;
5895     }
5897     while (<INPUT>) {
5898        chomp;
5899        my ($object, @ifaces) = split;
5900        if (exists($KnownSymbols{$object}) && $KnownSymbols{$object} == 1) {
5901            my @knownIfaces = ();
5903            # filter out private interfaces, but leave foreign interfaces
5904            foreach my $iface (@ifaces) {
5905                if (!exists($KnownSymbols{$iface}) || $KnownSymbols{$iface} == 1) {
5906                    push (@knownIfaces, $iface);
5907                }
5908              }
5910            $Interfaces{$object} = join(' ', @knownIfaces);
5911            @TRACE@("Interfaces for $object: $Interfaces{$object}\n");
5912        } else {
5913          @TRACE@("skipping interfaces for unknown symbol: $object\n");
5914        }
5915     }
5916     close (INPUT);
5919 #############################################################################
5920 # Function    : ReadPrerequisites
5921 # Description : This reads in the $MODULE.prerequisites file.
5923 # Arguments   : none
5924 #############################################################################
5926 sub ReadPrerequisites {
5927     %Prerequisites = ();
5929     if (! -f $PREREQUISITES_FILE) {
5930         return;
5931     }
5932     if (!open (INPUT, $PREREQUISITES_FILE)) {
5933         warn "Can't open $PREREQUISITES_FILE - skipping prerequisites\n";
5934         return;
5935     }
5937     while (<INPUT>) {
5938        chomp;
5939        my ($iface, @prereqs) = split;
5940        if (exists($KnownSymbols{$iface}) && $KnownSymbols{$iface} == 1) {
5941            my @knownPrereqs = ();
5943            # filter out private prerequisites, but leave foreign prerequisites
5944            foreach my $prereq (@prereqs) {
5945                if (!exists($KnownSymbols{$prereq}) || $KnownSymbols{$prereq} == 1) {
5946                   push (@knownPrereqs, $prereq);
5947                }
5948            }
5950            $Prerequisites{$iface} = join(' ', @knownPrereqs);
5951        }
5952     }
5953     close (INPUT);
5956 #############################################################################
5957 # Function    : ReadArgsFile
5958 # Description : This reads in an existing file which contains information on
5959 #                all GTK args. It creates the arrays @ArgObjects, @ArgNames,
5960 #                @ArgTypes, @ArgFlags, @ArgNicks and @ArgBlurbs containing info
5961 #               on the args.
5962 # Arguments   : $file - the file containing the arg information.
5963 #############################################################################
5965 sub ReadArgsFile {
5966     my ($file) = @_;
5968     my $in_arg = 0;
5969     my $arg_object;
5970     my $arg_name;
5971     my $arg_type;
5972     my $arg_flags;
5973     my $arg_nick;
5974     my $arg_blurb;
5975     my $arg_default;
5976     my $arg_range;
5978     # Reset the args info.
5979     @ArgObjects = ();
5980     @ArgNames = ();
5981     @ArgTypes = ();
5982     @ArgFlags = ();
5983     @ArgNicks = ();
5984     @ArgBlurbs = ();
5985     @ArgDefaults = ();
5986     @ArgRanges = ();
5988     if (! -f $file) {
5989         return;
5990     }
5991     if (!open (INPUT, $file)) {
5992         warn "Can't open $file - skipping args\n";
5993         return;
5994     }
5995     while (<INPUT>) {
5996         if (!$in_arg) {
5997             if (m/^<ARG>/) {
5998                 $in_arg = 1;
5999                 $arg_object = "";
6000                 $arg_name = "";
6001                 $arg_type = "";
6002                 $arg_flags = "";
6003                 $arg_nick = "";
6004                 $arg_blurb = "";
6005                 $arg_default = "";
6006                 $arg_range = "";
6007             }
6008         } else {
6009             if (m/^<NAME>(.*)<\/NAME>/) {
6010                 $arg_name = $1;
6011                 if ($arg_name =~ m/^(.*)::(.*)$/) {
6012                     $arg_object = $1;
6013                     ($arg_name = $2) =~ s/_/-/g;
6014                     @TRACE@("Found arg: $arg_name\n");
6015                 } else {
6016                     &LogWarning ($file, $., "Invalid argument name: $arg_name");
6017                 }
6018             } elsif (m/^<TYPE>(.*)<\/TYPE>/) {
6019                 $arg_type = $1;
6020             } elsif (m/^<RANGE>(.*)<\/RANGE>/) {
6021                 $arg_range = $1;
6022             } elsif (m/^<FLAGS>(.*)<\/FLAGS>/) {
6023                 $arg_flags = $1;
6024             } elsif (m/^<NICK>(.*)<\/NICK>/) {
6025                 $arg_nick = $1;
6026             } elsif (m/^<BLURB>(.*)<\/BLURB>/) {
6027                 $arg_blurb = $1;
6028                 if ($arg_blurb eq "(null)") {
6029                   $arg_blurb = "";
6030                   &LogWarning ($file, $., "Property ${arg_object}:${arg_name} has no documentation.");
6031                 }
6032             } elsif (m/^<DEFAULT>(.*)<\/DEFAULT>/) {
6033                 $arg_default = $1;
6034             } elsif (m%^</ARG>%) {
6035                 @TRACE@("Found end of arg: ${arg_object}::${arg_name}\n${arg_type} : ${arg_flags}\n");
6036                 push (@ArgObjects, $arg_object);
6037                 push (@ArgNames, $arg_name);
6038                 push (@ArgTypes, $arg_type);
6039                 push (@ArgRanges, $arg_range);
6040                 push (@ArgFlags, $arg_flags);
6041                 push (@ArgNicks, $arg_nick);
6042                 push (@ArgBlurbs, $arg_blurb);
6043                 push (@ArgDefaults, $arg_default);
6044                 $in_arg = 0;
6045             }
6046         }
6047     }
6048     close (INPUT);
6051 #############################################################################
6052 # Function    : AddTreeLineArt
6053 # Description : Add unicode lineart to a pre-indented string array and returns
6054 #               it as as multiline string.
6055 # Arguments   : @tree - array of indented strings.
6056 #############################################################################
6058 sub AddTreeLineArt {
6059   my @tree = @{$_[0]};
6060   my $i;
6061   my $j;
6062   my $indent;
6063   
6064   # iterate bottom up over the tree 
6065   for ($i = $#tree; $i >= 0; $i--) {
6066     # count leading spaces
6067     $tree[$i] =~ /^([^<A-Za-z]*)/;
6068     $indent = length( $1 );
6069     # replace with ╰───, if place of ╰ is not space insert ├
6070     if ($indent > 4) {
6071       if (substr($tree[$i],$indent-4,1) eq " ") {
6072         substr($tree[$i],$indent-4,4) = "--- ";
6073       } else {
6074         substr($tree[$i],$indent-4,4) = "+-- ";
6075       }
6076       # go lines up while space and insert |
6077       for ($j = $i - 1; ($j >= 0 && substr($tree[$j],$indent-4,1) eq ' '); $j--) {
6078         substr($tree[$j],$indent-4,1) = '|';
6079       }
6080     }
6081   }
6082   
6083   my $res = join("\n", @tree);
6084   # unicode chars for: ╰──
6085   $res =~ s%---%<phrase role=\"lineart\">&#9584;&#9472;&#9472;</phrase>%g;
6086   # unicde chars for: ├──
6087   $res =~ s%\+--%<phrase role=\"lineart\">&#9500;&#9472;&#9472;</phrase>%g;
6088   # unicode char for: │
6089   $res =~ s%\|%<phrase role=\"lineart\">&#9474;</phrase>%g;
6090   
6091   return $res;
6095 #############################################################################
6096 # Function    : CheckIsObject
6097 # Description : Returns 1 if the given name is a GObject or a subclass.
6098 #                It uses the global @Objects array.
6099 #                Note that the @Objects array only contains classes in the
6100 #                current module and their ancestors - not all GObject classes.
6101 # Arguments   : $name - the name to check.
6102 #############################################################################
6104 sub CheckIsObject {
6105     my ($name) = @_;
6106     my $root = $ObjectRoots{$name};
6107     # Let GBoxed pass as an object here to get -struct appended to the id
6108     # and prevent conflicts with sections.
6109     return (defined($root) and $root ne 'GEnum' and $root ne 'GFlags');
6113 #############################################################################
6114 # Function    : MakeReturnField
6115 # Description : Pads a string to $RETURN_TYPE_FIELD_WIDTH.
6116 # Arguments   : $str - the string to pad.
6117 #############################################################################
6119 sub MakeReturnField {
6120     my ($str) = @_;
6122     return $str . (' ' x ($RETURN_TYPE_FIELD_WIDTH - length ($str)));
6125 #############################################################################
6126 # Function    : GetSymbolSourceFile
6127 # Description : Get the filename where the symbol docs where taken from.
6128 # Arguments   : $symbol - the symbol name
6129 #############################################################################
6131 sub GetSymbolSourceFile {
6132     my ($symbol) = @_;
6134     if (defined($SourceSymbolSourceFile{$symbol})) {
6135         return $SourceSymbolSourceFile{$symbol};
6136     } elsif (defined($SymbolSourceFile{$symbol})) {
6137         return $SymbolSourceFile{$symbol};
6138     } else {
6139         return "";
6140     }
6143 #############################################################################
6144 # Function    : GetSymbolSourceLine
6145 # Description : Get the file line where the symbol docs where taken from.
6146 # Arguments   : $symbol - the symbol name
6147 #############################################################################
6149 sub GetSymbolSourceLine {
6150     my ($symbol) = @_;
6152     if (defined($SourceSymbolSourceLine{$symbol})) {
6153         return $SourceSymbolSourceLine{$symbol};
6154     } elsif (defined($SymbolSourceLine{$symbol})) {
6155         return $SymbolSourceLine{$symbol};
6156     } else {
6157         return 0;
6158     }