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