mshtml: Added IHTMLDocument2::put_designMode tests.
[wine] / tools / c2man.pl
1 #! /usr/bin/perl -w
2 #
3 # Generate API documentation. See http://www.winehq.org/docs/winedev-guide/api-docs for details.
4 #
5 # Copyright (C) 2000 Mike McCormack
6 # Copyright (C) 2003 Jon Griffiths
7 #
8 # This library is free software; you can redistribute it and/or
9 # modify it under the terms of the GNU Lesser General Public
10 # License as published by the Free Software Foundation; either
11 # version 2.1 of the License, or (at your option) any later version.
12 #
13 # This library 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 GNU
16 # Lesser General Public License for more details.
17 #
18 # You should have received a copy of the GNU Lesser General Public
19 # License along with this library; if not, write to the Free Software
20 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
21 #
22 # TODO
23 #  Consolidate A+W pairs together, and only write one doc, without the suffix
24 #  Implement automatic docs of structs/defines in headers
25 #  SGML gurus - feel free to smarten up the SGML.
26 #  Add any other relevant information for the dll - imports etc
27 #  Should we have a special output mode for WineHQ?
28
29 use strict;
30 use bytes;
31
32 # Function flags. most of these come from the spec flags
33 my $FLAG_DOCUMENTED = 1;
34 my $FLAG_NONAME     = 2;
35 my $FLAG_I386       = 4;
36 my $FLAG_REGISTER   = 8;
37 my $FLAG_APAIR      = 16; # The A version of a matching W function
38 my $FLAG_WPAIR      = 32; # The W version of a matching A function
39 my $FLAG_64PAIR     = 64; # The 64 bit version of a matching 32 bit function
40
41 # Export list slot labels.
42 my $EXPORT_ORDINAL  = 0;  # Ordinal.
43 my $EXPORT_CALL     = 1;  # Call type.
44 my $EXPORT_EXPNAME  = 2;  # Export name.
45 my $EXPORT_IMPNAME  = 3;  # Implementation name.
46 my $EXPORT_FLAGS    = 4;  # Flags - see above.
47
48 # Options
49 my $opt_output_directory = "man3w"; # All default options are for nroff (man pages)
50 my $opt_manual_section = "3w";
51 my $opt_source_dir = "";
52 my $opt_wine_root_dir = "";
53 my $opt_output_format = "";         # '' = nroff, 'h' = html, 's' = sgml, 'x' = xml
54 my $opt_output_empty = 0;           # Non-zero = Create 'empty' comments (for every implemented function)
55 my $opt_fussy = 1;                  # Non-zero = Create only if we have a RETURNS section
56 my $opt_verbose = 0;                # >0 = verbosity. Can be given multiple times (for debugging)
57 my @opt_header_file_list = ();
58 my @opt_spec_file_list = ();
59 my @opt_source_file_list = ();
60
61 # All the collected details about all the .spec files being processed
62 my %spec_files;
63 # All the collected details about all the source files being processed
64 my %source_files;
65 # All documented functions that are to be placed in the index
66 my @index_entries_list = ();
67
68 # useful globals
69 my $pwd = `pwd`."/";
70 $pwd =~ s/\n//;
71 my @datetime = localtime;
72 my @months = ( "Jan", "Feb", "Mar", "Apr", "May", "Jun",
73                "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" );
74 my $year = $datetime[5] + 1900;
75 my $date = "$months[$datetime[4]] $year";
76
77
78 sub output_api_comment($);
79 sub output_api_footer($);
80 sub output_api_header($);
81 sub output_api_name($);
82 sub output_api_synopsis($);
83 sub output_close_api_file();
84 sub output_comment($);
85 sub output_html_index_files();
86 sub output_html_stylesheet();
87 sub output_open_api_file($);
88 sub output_sgml_dll_file($);
89 sub output_xml_dll_file($);
90 sub output_sgml_master_file($);
91 sub output_xml_master_file($);
92 sub output_spec($);
93 sub process_comment($);
94 sub process_extra_comment($);
95
96
97 # Generate the list of exported entries for the dll
98 sub process_spec_file($)
99 {
100   my $spec_name = shift;
101   my ($dll_name, $dll_ext)  = split(/\./, $spec_name);
102   $dll_ext = "dll" if ( $dll_ext eq "spec" );
103   my $uc_dll_name  = uc $dll_name;
104
105   my $spec_details =
106   {
107     NAME => $spec_name,
108     DLL_NAME => $dll_name,
109     DLL_EXT => $dll_ext,
110     NUM_EXPORTS => 0,
111     NUM_STUBS => 0,
112     NUM_FUNCS => 0,
113     NUM_FORWARDS => 0,
114     NUM_VARS => 0,
115     NUM_DOCS => 0,
116     CONTRIBUTORS => [ ],
117     SOURCES => [ ],
118     DESCRIPTION => [ ],
119     EXPORTS => [ ],
120     EXPORTED_NAMES => { },
121     IMPLEMENTATION_NAMES => { },
122     EXTRA_COMMENTS => [ ],
123     CURRENT_EXTRA => [ ] ,
124   };
125
126   if ($opt_verbose > 0)
127   {
128     print "Processing ".$spec_name."\n";
129   }
130
131   # We allow opening to fail just to cater for the peculiarities of
132   # the Wine build system. This doesn't hurt, in any case
133   open(SPEC_FILE, "<$spec_name")
134   || (($opt_source_dir ne "")
135       && open(SPEC_FILE, "<$opt_source_dir/$spec_name"))
136   || return;
137
138   while(<SPEC_FILE>)
139   {
140     s/^\s+//;            # Strip leading space
141     s/\s+\n$/\n/;        # Strip trailing space
142     s/\s+/ /g;           # Strip multiple tabs & spaces to a single space
143     s/\s*#.*//;          # Strip comments
144     s/\(.*\)/ /;         # Strip arguments
145     s/\s+/ /g;           # Strip multiple tabs & spaces to a single space (again)
146     s/\n$//;             # Strip newline
147
148     my $flags = 0;
149     if( /\-noname/ )
150     {
151       $flags |= $FLAG_NONAME;
152     }
153     if( /\-i386/ )
154     {
155       $flags |= $FLAG_I386;
156     }
157     if( /\-register/ )
158     {
159       $flags |= $FLAG_REGISTER;
160     }
161     s/ \-[a-z0-9=_]+//g;   # Strip flags
162
163     if( /^(([0-9]+)|@) / )
164     {
165       # This line contains an exported symbol
166       my ($ordinal, $call_convention, $exported_name, $implementation_name) = split(' ');
167
168       for ($call_convention)
169       {
170         /^(cdecl|stdcall|varargs|pascal)$/
171                  && do { $spec_details->{NUM_FUNCS}++;    last; };
172         /^(variable|equate)$/
173                  && do { $spec_details->{NUM_VARS}++;     last; };
174         /^(extern)$/
175                  && do { $spec_details->{NUM_FORWARDS}++; last; };
176         /^stub$/ && do { $spec_details->{NUM_STUBS}++;    last; };
177         if ($opt_verbose > 0)
178         {
179           print "Warning: didn't recognise convention \'",$call_convention,"'\n";
180         }
181         last;
182       }
183
184       # Convert ordinal only names so we can find them later
185       if ($exported_name eq "@")
186       {
187         $exported_name = $uc_dll_name.'_'.$ordinal;
188       }
189       if (!defined($implementation_name))
190       {
191         $implementation_name = $exported_name;
192       }
193       if ($implementation_name eq "")
194       {
195         $implementation_name = $exported_name;
196       }
197
198       if ($implementation_name =~ /(.*?)\./)
199       {
200         $call_convention = "forward"; # Referencing a function from another dll
201         $spec_details->{NUM_FUNCS}--;
202         $spec_details->{NUM_FORWARDS}++;
203       }
204
205       # Add indices for the exported and implementation names
206       $spec_details->{EXPORTED_NAMES}{$exported_name} = $spec_details->{NUM_EXPORTS};
207       if ($implementation_name ne $exported_name)
208       {
209         $spec_details->{IMPLEMENTATION_NAMES}{$exported_name} = $spec_details->{NUM_EXPORTS};
210       }
211
212       # Add the exported entry
213       $spec_details->{NUM_EXPORTS}++;
214       my @export = ($ordinal, $call_convention, $exported_name, $implementation_name, $flags);
215       push (@{$spec_details->{EXPORTS}},[@export]);
216     }
217   }
218   close(SPEC_FILE);
219
220   # Add this .spec files details to the list of .spec files
221   $spec_files{$uc_dll_name} = [$spec_details];
222 }
223
224 # Read each source file, extract comments, and generate API documentation if appropriate
225 sub process_source_file($)
226 {
227   my $source_file = shift;
228   my $source_details =
229   {
230     CONTRIBUTORS => [ ],
231     DEBUG_CHANNEL => "",
232   };
233   my $comment =
234   {
235     FILE => $source_file,
236     COMMENT_NAME => "",
237     ALT_NAME => "",
238     DLL_NAME => "",
239     DLL_EXT => "",
240     ORDINAL => "",
241     RETURNS => "",
242     PROTOTYPE => [],
243     TEXT => [],
244   };
245   my $parse_state = 0;
246   my $ignore_blank_lines = 1;
247   my $extra_comment = 0; # 1 if this is an extra comment, i.e its not a .spec export
248
249   if ($opt_verbose > 0)
250   {
251     print "Processing ".$source_file."\n";
252   }
253   open(SOURCE_FILE,"<$source_file")
254   || (($opt_source_dir ne "")
255       && open(SOURCE_FILE,"<$opt_source_dir/$source_file"))
256   || die "couldn't open ".$source_file."\n";
257
258   # Add this source file to the list of source files
259   $source_files{$source_file} = [$source_details];
260
261   while(<SOURCE_FILE>)
262   {
263     s/\n$//;   # Strip newline
264     s/^\s+//;  # Strip leading space
265     s/\s+$//;  # Strip trailing space
266     if (! /^\*\|/ )
267     {
268       # Strip multiple tabs & spaces to a single space
269       s/\s+/ /g;
270     }
271
272     if ( / +Copyright *(\([Cc]\))*[0-9 \-\,\/]*([[:alpha:][:^ascii:] \.\-]+)/ )
273     {
274       # Extract a contributor to this file
275       my $contributor = $2;
276       $contributor =~ s/ *$//;
277       $contributor =~ s/^by //;
278       $contributor =~ s/\.$//;
279       $contributor =~ s/ (for .*)/ \($1\)/;
280       if ($contributor ne "")
281       {
282         if ($opt_verbose > 3)
283         {
284           print "Info: Found contributor:'".$contributor."'\n";
285         }
286         push (@{$source_details->{CONTRIBUTORS}},$contributor);
287       }
288     }
289     elsif ( /WINE_DEFAULT_DEBUG_CHANNEL\(([A-Za-z]*)\)/ )
290     {
291       # Extract the debug channel to use
292       if ($opt_verbose > 3)
293       {
294         print "Info: Found debug channel:'".$1."'\n";
295       }
296       $source_details->{DEBUG_CHANNEL} = $1;
297     }
298
299     if ($parse_state == 0) # Searching for a comment
300     {
301       if ( /^\/\**$/ )
302       {
303         # This file is used by the DLL - Make sure we get our contributors right
304         @{$spec_files{$comment->{DLL_NAME}}[0]->{CURRENT_EXTRA}} = ();
305         push (@{$spec_files{$comment->{DLL_NAME}}[0]->{SOURCES}},$comment->{FILE});
306         # Found a comment start
307         $comment->{COMMENT_NAME} = "";
308         $comment->{ALT_NAME} = "";
309         $comment->{DLL_NAME} = "";
310         $comment->{ORDINAL} = "";
311         $comment->{RETURNS} = "";
312         $comment->{PROTOTYPE} = [];
313         $comment->{TEXT} = [];
314         $ignore_blank_lines = 1;
315         $extra_comment = 0;
316         $parse_state = 3;
317       }
318     }
319     elsif ($parse_state == 1) # Reading in a comment
320     {
321       if ( /^\**\// )
322       {
323         # Found the end of the comment
324         $parse_state = 2;
325       }
326       elsif ( s/^\*\|/\|/ )
327       {
328         # A line of comment not meant to be pre-processed
329         push (@{$comment->{TEXT}},$_); # Add the comment text
330       }
331       elsif ( s/^ *\** *// )
332       {
333         # A line of comment, starting with an asterisk
334         if ( /^[A-Z]+$/ || $_ eq "")
335         {
336           # This is a section start, so skip blank lines before and after it.
337           my $last_line = pop(@{$comment->{TEXT}});
338           if (defined($last_line) && $last_line ne "")
339           {
340             # Put it back
341             push (@{$comment->{TEXT}},$last_line);
342           }
343           if ( /^[A-Z]+$/ )
344           {
345             $ignore_blank_lines = 1;
346           }
347           else
348           {
349             $ignore_blank_lines = 0;
350           }
351         }
352
353         if ($ignore_blank_lines == 0 || $_ ne "")
354         {
355           push (@{$comment->{TEXT}},$_); # Add the comment text
356         }
357       }
358       else
359       {
360         # This isn't a well formatted comment: look for the next one
361         $parse_state = 0;
362       }
363     }
364     elsif ($parse_state == 2) # Finished reading in a comment
365     {
366       if ( /(WINAPIV|WINAPI|__cdecl|PASCAL|CALLBACK|FARPROC16)/ ||
367            /.*?\(/ )
368       {
369         # Comment is followed by a function definition
370         $parse_state = 4; # Fall through to read prototype
371       }
372       else
373       {
374         # Allow cpp directives and blank lines between the comment and prototype
375         if ($extra_comment == 1)
376         {
377           # An extra comment not followed by a function definition
378           $parse_state = 5; # Fall through to process comment
379         }
380         elsif (!/^\#/ && !/^ *$/ && !/^__ASM_GLOBAL_FUNC/)
381         {
382           # This isn't a well formatted comment: look for the next one
383           if ($opt_verbose > 1)
384           {
385             print "Info: Function '",$comment->{COMMENT_NAME},"' not followed by prototype.\n";
386           }
387           $parse_state = 0;
388         }
389       }
390     }
391     elsif ($parse_state == 3) # Reading in the first line of a comment
392     {
393       s/^ *\** *//;
394       if ( /^([\@A-Za-z0-9_]+) +(\(|\[)([A-Za-z0-9_]+)\.(([0-9]+)|@)(\)|\])\s*(.*)$/ )
395       {
396         # Found a correctly formed "ApiName (DLLNAME.Ordinal)" line.
397         if (defined ($7) && $7 ne "")
398         {
399           push (@{$comment->{TEXT}},$_); # Add the trailing comment text
400         }
401         $comment->{COMMENT_NAME} = $1;
402         $comment->{DLL_NAME} = uc $3;
403         $comment->{ORDINAL} = $4;
404         $comment->{DLL_NAME} =~ s/^KERNEL$/KRNL386/; # Too many of these to ignore, _old_ code
405         $parse_state = 1;
406       }
407       elsif ( /^([A-Za-z0-9_-]+) +\{([A-Za-z0-9_]+)\}$/ )
408       {
409         # Found a correctly formed "CommentTitle {DLLNAME}" line (extra documentation)
410         $comment->{COMMENT_NAME} = $1;
411         $comment->{DLL_NAME} = uc $2;
412         $comment->{ORDINAL} = "";
413         $extra_comment = 1;
414         $parse_state = 1;
415       }
416       else
417       {
418         # This isn't a well formatted comment: look for the next one
419         $parse_state = 0;
420       }
421     }
422
423     if ($parse_state == 4) # Reading in the function definition
424     {
425       push (@{$comment->{PROTOTYPE}},$_);
426       # Strip comments from the line before checking for ')'
427       my $stripped_line = $_;
428       $stripped_line =~ s/ *(\/\* *)(.*?)( *\*\/ *)//;
429       if ( $stripped_line =~ /\)/ )
430       {
431         # Strip a blank last line
432         my $last_line = pop(@{$comment->{TEXT}});
433         if (defined($last_line) && $last_line ne "")
434         {
435           # Put it back
436           push (@{$comment->{TEXT}},$last_line);
437         }
438
439         if ($opt_output_empty != 0 && @{$comment->{TEXT}} == 0)
440         {
441           # Create a 'not implemented' comment
442           @{$comment->{TEXT}} = ("fixme: This function has not yet been documented.");
443         }
444         $parse_state = 5;
445       }
446     }
447
448     if ($parse_state == 5) # Processing the comment
449     {
450       # Process it, if it has any text
451       if (@{$comment->{TEXT}} > 0)
452       {
453         if ($extra_comment == 1)
454         {
455           process_extra_comment($comment);
456         }
457         else
458         {
459           @{$comment->{TEXT}} = ("DESCRIPTION", @{$comment->{TEXT}});
460           process_comment($comment);
461         }
462       }
463       elsif ($opt_verbose > 1 && $opt_output_empty == 0)
464       {
465         print "Info: Function '",$comment->{COMMENT_NAME},"' has no documentation.\n";
466       }
467       $parse_state = 0;
468     }
469   }
470   close(SOURCE_FILE);
471 }
472
473 # Standardise a comments text for consistency
474 sub process_comment_text($)
475 {
476   my $comment = shift;
477   my $in_params = 0;
478   my @tmp_list = ();
479   my $i = 0;
480
481   for (@{$comment->{TEXT}})
482   {
483     my $line = $_;
484
485     if ( /^\s*$/ || /^[A-Z]+$/ || /^-/ )
486     {
487       $in_params = 0;
488     }
489     if ( $in_params > 0 && !/\[/ && !/\]/ )
490     {
491       # Possibly a continuation of the parameter description
492       my $last_line = pop(@tmp_list);
493       if ( $last_line =~ /\[/ && $last_line =~ /\]/ )
494       {
495         $line = $last_line." ".$_;
496       }
497       else
498       {
499         $in_params = 0;
500         push (@tmp_list, $last_line);
501       }
502     }
503     if ( /^(PARAMS|MEMBERS)$/ )
504     {
505       $in_params = 1;
506     }
507     push (@tmp_list, $line);
508   }
509
510   @{$comment->{TEXT}} = @tmp_list;
511
512   for (@{$comment->{TEXT}})
513   {
514     if (! /^\|/ )
515     {
516       # Map I/O values. These come in too many formats to standardise now....
517       s/\[I\]|\[i\]|\[in\]|\[IN\]/\[In\] /g;
518       s/\[O\]|\[o\]|\[out\]|\[OUT\]/\[Out\]/g;
519       s/\[I\/O\]|\[I\,O\]|\[i\/o\]|\[in\/out\]|\[IN\/OUT\]/\[In\/Out\]/g;
520       # TRUE/FALSE/NULL are defines, capitilise them
521       s/True|true/TRUE/g;
522       s/False|false/FALSE/g;
523       s/Null|null/NULL/g;
524       # Preferred capitalisations
525       s/ wine| WINE/ Wine/g;
526       s/ API | api / Api /g;
527       s/ DLL | Dll / dll /g;
528       s/ URL | url / Url /g;
529       s/WIN16|win16/Win16/g;
530       s/WIN32|win32/Win32/g;
531       s/WIN64|win64/Win64/g;
532       s/ ID | id / Id /g;
533       # Grammar
534       s/([a-z])\.([A-Z])/$1\. $2/g; # Space after full stop
535       s/ \:/\:/g;                   # Colons to the left
536       s/ \;/\;/g;                   # Semi-colons too
537       # Common idioms
538       s/^See ([A-Za-z0-9_]+)\.$/See $1\(\)\./;                # Referring to A version from W
539       s/^Unicode version of ([A-Za-z0-9_]+)\.$/See $1\(\)\./; # Ditto
540       s/^64\-bit version of ([A-Za-z0-9_]+)\.$/See $1\(\)\./; # Referring to 32 bit version from 64
541       s/^PARAMETERS$/PARAMS/;  # Name of parameter section should be 'PARAMS'
542       # Trademarks
543       s/( |\.)(M\$|MS|Microsoft|microsoft|micro\$oft|Micro\$oft)( |\.)/$1Microsoft\(tm\)$3/g;
544       s/( |\.)(Windows|windows|windoze|winblows)( |\.)/$1Windows\(tm\)$3/g;
545       s/( |\.)(DOS|dos|msdos)( |\.)/$1MS-DOS\(tm\)$3/g;
546       s/( |\.)(UNIX|unix)( |\.)/$1Unix\(tm\)$3/g;
547       s/( |\.)(LINIX|linux)( |\.)/$1Linux\(tm\)$3/g;
548       # Abbreviations
549       s/( char )/ character /g;
550       s/( chars )/ characters /g;
551       s/( info )/ information /g;
552       s/( app )/ application /g;
553       s/( apps )/ applications /g;
554       s/( exe )/ executable /g;
555       s/( ptr )/ pointer /g;
556       s/( obj )/ object /g;
557       s/( err )/ error /g;
558       s/( bool )/ boolean /g;
559       s/( no\. )/ number /g;
560       s/( No\. )/ Number /g;
561       # Punctuation
562       if ( /\[I|\[O/ && ! /\.$/ )
563       {
564         $_ = $_."."; # Always have a full stop at the end of parameter desc.
565       }
566       elsif ($i > 0 && /^[A-Z]*$/  &&
567                !(@{$comment->{TEXT}}[$i-1] =~ /\.$/) &&
568                !(@{$comment->{TEXT}}[$i-1] =~ /\:$/))
569       {
570
571         if (!(@{$comment->{TEXT}}[$i-1] =~ /^[A-Z]*$/))
572         {
573           # Paragraphs always end with a full stop
574           @{$comment->{TEXT}}[$i-1] = @{$comment->{TEXT}}[$i-1].".";
575         }
576       }
577     }
578     $i++;
579   }
580 }
581
582 # Standardise our comment and output it if it is suitable.
583 sub process_comment($)
584 {
585   my $comment = shift;
586
587   # Don't process this comment if the function isn't exported
588   my $spec_details = $spec_files{$comment->{DLL_NAME}}[0];
589
590   if (!defined($spec_details))
591   {
592     if ($opt_verbose > 2)
593     {
594       print "Warning: Function '".$comment->{COMMENT_NAME}."' belongs to '".
595             $comment->{DLL_NAME}."' (not passed with -w): not processing it.\n";
596     }
597     return;
598   }
599
600   if ($comment->{COMMENT_NAME} eq "@")
601   {
602     my $found = 0;
603
604     # Find the name from the .spec file
605     for (@{$spec_details->{EXPORTS}})
606     {
607       if (@$_[$EXPORT_ORDINAL] eq $comment->{ORDINAL})
608       {
609         $comment->{COMMENT_NAME} = @$_[$EXPORT_EXPNAME];
610         $found = 1;
611       }
612     }
613
614     if ($found == 0)
615     {
616       # Create an implementation name
617       $comment->{COMMENT_NAME} = $comment->{DLL_NAME}."_".$comment->{ORDINAL};
618     }
619   }
620
621   my $exported_names = $spec_details->{EXPORTED_NAMES};
622   my $export_index = $exported_names->{$comment->{COMMENT_NAME}};
623   my $implementation_names = $spec_details->{IMPLEMENTATION_NAMES};
624
625   if (!defined($export_index))
626   {
627     # Perhaps the comment uses the implementation name?
628     $export_index = $implementation_names->{$comment->{COMMENT_NAME}};
629   }
630   if (!defined($export_index))
631   {
632     # This function doesn't appear to be exported. hmm.
633     if ($opt_verbose > 2)
634     {
635       print "Warning: Function '".$comment->{COMMENT_NAME}."' claims to belong to '".
636             $comment->{DLL_NAME}."' but is not exported by it: not processing it.\n";
637     }
638     return;
639   }
640
641   # When the function is exported twice we have the second name below the first
642   # (you see this a lot in ntdll, but also in some other places).
643   my $first_line = ${$comment->{TEXT}}[1];
644
645   if ( $first_line =~ /^(@|[A-Za-z0-9_]+) +(\(|\[)([A-Za-z0-9_]+)\.(([0-9]+)|@)(\)|\])$/ )
646   {
647     # Found a second name - mark it as documented
648     my $alt_index = $exported_names->{$1};
649     if (defined($alt_index))
650     {
651       if ($opt_verbose > 2)
652       {
653         print "Info: Found alternate name '",$1,"\n";
654       }
655       my $alt_export = @{$spec_details->{EXPORTS}}[$alt_index];
656       @$alt_export[$EXPORT_FLAGS] |= $FLAG_DOCUMENTED;
657       $spec_details->{NUM_DOCS}++;
658       ${$comment->{TEXT}}[1] = "";
659     }
660   }
661
662   if (@{$spec_details->{CURRENT_EXTRA}})
663   {
664     # We have an extra comment that might be related to this one
665     my $current_comment = ${$spec_details->{CURRENT_EXTRA}}[0];
666     my $current_name = $current_comment->{COMMENT_NAME};
667     if ($comment->{COMMENT_NAME} =~ /^$current_name/ && $comment->{COMMENT_NAME} ne $current_name)
668     {
669       if ($opt_verbose > 2)
670       {
671         print "Linking ",$comment->{COMMENT_NAME}," to $current_name\n";
672       }
673       # Add a reference to this comment to our extra comment
674       push (@{$current_comment->{TEXT}}, $comment->{COMMENT_NAME}."()","");
675     }
676   }
677
678   # We want our docs generated using the implementation name, so they are unique
679   my $export = @{$spec_details->{EXPORTS}}[$export_index];
680   $comment->{COMMENT_NAME} = @$export[$EXPORT_IMPNAME];
681   $comment->{ALT_NAME} = @$export[$EXPORT_EXPNAME];
682
683   # Mark the function as documented
684   $spec_details->{NUM_DOCS}++;
685   @$export[$EXPORT_FLAGS] |= $FLAG_DOCUMENTED;
686
687   # If we have parameter comments in the prototype, extract them
688   my @parameter_comments;
689   for (@{$comment->{PROTOTYPE}})
690   {
691     s/ *\, */\,/g; # Strip spaces from around commas
692
693     if ( s/ *(\/\* *)(.*?)( *\*\/ *)// ) # Strip out comment
694     {
695       my $parameter_comment = $2;
696       if (!$parameter_comment =~ /^\[/ )
697       {
698         # Add [IO] markers so we format the comment correctly
699         $parameter_comment = "[fixme] ".$parameter_comment;
700       }
701       if ( /( |\*)([A-Za-z_]{1}[A-Za-z_0-9]*)(\,|\))/ )
702       {
703         # Add the parameter name
704         $parameter_comment = $2." ".$parameter_comment;
705       }
706       push (@parameter_comments, $parameter_comment);
707     }
708   }
709
710   # If we extracted any prototype comments, add them to the comment text.
711   if (@parameter_comments)
712   {
713     @parameter_comments = ("PARAMS", @parameter_comments);
714     my @new_comment = ();
715     my $inserted_params = 0;
716
717     for (@{$comment->{TEXT}})
718     {
719       if ( $inserted_params == 0 && /^[A-Z]+$/ )
720       {
721         # Found a section header, so this is where we insert
722         push (@new_comment, @parameter_comments);
723         $inserted_params = 1;
724       }
725       push (@new_comment, $_);
726     }
727     if ($inserted_params == 0)
728     {
729       # Add them to the end
730       push (@new_comment, @parameter_comments);
731     }
732     $comment->{TEXT} = [@new_comment];
733   }
734
735   if ($opt_fussy == 1 && $opt_output_empty == 0)
736   {
737     # Reject any comment that doesn't have a description or a RETURNS section.
738     # This is the default for now, 'coz many comments aren't suitable.
739     my $found_returns = 0;
740     my $found_description_text = 0;
741     my $in_description = 0;
742     for (@{$comment->{TEXT}})
743     {
744       if ( /^RETURNS$/ )
745       {
746         $found_returns = 1;
747         $in_description = 0;
748       }
749       elsif ( /^DESCRIPTION$/ )
750       {
751         $in_description = 1;
752       }
753       elsif ($in_description == 1)
754       {
755         if ( !/^[A-Z]+$/ )
756         {
757           # Don't reject comments that refer to another doc (e.g. A/W)
758           if ( /^See ([A-Za-z0-9_]+)\.$/ )
759           {
760             if ($comment->{COMMENT_NAME} =~ /W$/ )
761             {
762               # This is probably a Unicode version of an Ascii function.
763               # Create the Ascii name and see if its been documented
764               my $ascii_name = $comment->{COMMENT_NAME};
765               $ascii_name =~ s/W$/A/;
766
767               my $ascii_export_index = $exported_names->{$ascii_name};
768
769               if (!defined($ascii_export_index))
770               {
771                 $ascii_export_index = $implementation_names->{$ascii_name};
772               }
773               if (!defined($ascii_export_index))
774               {
775                 if ($opt_verbose > 2)
776                 {
777                   print "Warning: Function '".$comment->{COMMENT_NAME}."' is not an A/W pair.\n";
778                 }
779               }
780               else
781               {
782                 my $ascii_export = @{$spec_details->{EXPORTS}}[$ascii_export_index];
783                 if (@$ascii_export[$EXPORT_FLAGS] & $FLAG_DOCUMENTED)
784                 {
785                   # Flag these functions as an A/W pair
786                   @$ascii_export[$EXPORT_FLAGS] |= $FLAG_APAIR;
787                   @$export[$EXPORT_FLAGS] |= $FLAG_WPAIR;
788                 }
789               }
790             }
791             $found_returns = 1;
792           }
793           elsif ( /^Unicode version of ([A-Za-z0-9_]+)\.$/ )
794           {
795             @$export[$EXPORT_FLAGS] |= $FLAG_WPAIR; # Explicitly marked as W version
796             $found_returns = 1;
797           }
798           elsif ( /^64\-bit version of ([A-Za-z0-9_]+)\.$/ )
799           {
800             @$export[$EXPORT_FLAGS] |= $FLAG_64PAIR; # Explicitly marked as 64 bit version
801             $found_returns = 1;
802           }
803           $found_description_text = 1;
804         }
805         else
806         {
807           $in_description = 0;
808         }
809       }
810     }
811     if ($found_returns == 0 || $found_description_text == 0)
812     {
813       if ($opt_verbose > 2)
814       {
815         print "Info: Function '",$comment->{COMMENT_NAME},"' has no ",
816               "description and/or RETURNS section, skipping\n";
817       }
818       $spec_details->{NUM_DOCS}--;
819       @$export[$EXPORT_FLAGS] &= ~$FLAG_DOCUMENTED;
820       return;
821     }
822   }
823
824   process_comment_text($comment);
825
826   # Strip the prototypes return value, call convention, name and brackets
827   # (This leaves it as a list of types and names, or empty for void functions)
828   my $prototype = join(" ", @{$comment->{PROTOTYPE}});
829   $prototype =~ s/  / /g;
830
831   if ( $prototype =~ /(WINAPIV|WINAPI|__cdecl|PASCAL|CALLBACK|FARPROC16)/ )
832   {
833     $prototype =~ s/^(.*?)\s+(WINAPIV|WINAPI|__cdecl|PASCAL|CALLBACK|FARPROC16)\s+(.*?)\(\s*(.*)/$4/;
834     $comment->{RETURNS} = $1;
835   }
836   else
837   {
838     $prototype =~ s/^(.*?)([A-Za-z0-9_]+)\s*\(\s*(.*)/$3/;
839     $comment->{RETURNS} = $1;
840   }
841
842   $prototype =~ s/ *\).*//;        # Strip end bracket
843   $prototype =~ s/ *\* */\*/g;     # Strip space around pointers
844   $prototype =~ s/ *\, */\,/g;     # Strip space around commas
845   $prototype =~ s/^(void|VOID)$//; # If void, leave blank
846   $prototype =~ s/\*([A-Za-z_])/\* $1/g; # Separate pointers from parameter name
847   @{$comment->{PROTOTYPE}} = split ( /,/ ,$prototype);
848
849   # FIXME: If we have no parameters, make sure we have a PARAMS: None. section
850
851   # Find header file
852   my $h_file = "";
853   if (@$export[$EXPORT_FLAGS] & $FLAG_NONAME)
854   {
855     $h_file = "Exported by ordinal only. Use GetProcAddress() to obtain a pointer to the function.";
856   }
857   else
858   {
859     if ($comment->{COMMENT_NAME} ne "")
860     {
861       my $tmp = "grep -s -l $comment->{COMMENT_NAME} @opt_header_file_list 2>/dev/null";
862       $tmp = `$tmp`;
863       my $exit_value  = $? >> 8;
864       if ($exit_value == 0)
865       {
866         $tmp =~ s/\n.*//g;
867         if ($tmp ne "")
868         {
869           $h_file = "$tmp";
870           $h_file =~ s|^.*/\./||;
871         }
872       }
873     }
874     elsif ($comment->{ALT_NAME} ne "")
875     {
876       my $tmp = "grep -s -l $comment->{ALT_NAME} @opt_header_file_list"." 2>/dev/null";
877       $tmp = `$tmp`;
878       my $exit_value  = $? >> 8;
879       if ($exit_value == 0)
880       {
881         $tmp =~ s/\n.*//g;
882         if ($tmp ne "")
883         {
884           $h_file = "$tmp";
885           $h_file =~ s|^.*/\./||;
886         }
887       }
888     }
889     $h_file =~ s/^ *//;
890     $h_file =~ s/\n//;
891     if ($h_file eq "")
892     {
893       $h_file = "Not declared in a Wine header. The function is either undocumented, or missing from Wine."
894     }
895     else
896     {
897       $h_file = "Declared in \"".$h_file."\".";
898     }
899   }
900
901   # Find source file
902   my $c_file = $comment->{FILE};
903   if ($opt_wine_root_dir ne "")
904   {
905     my $cfile = $pwd."/".$c_file;     # Current dir + file
906     $cfile =~ s/(.+)(\/.*$)/$1/;      # Strip the filename
907     $cfile = `cd $cfile && pwd`;      # Strip any relative parts (e.g. "../../")
908     $cfile =~ s/\n//;                 # Strip newline
909     my $newfile = $c_file;
910     $newfile =~ s/(.+)(\/.*$)/$2/;    # Strip all but the filename
911     $cfile = $cfile."/".$newfile;     # Append filename to base path
912     $cfile =~ s/$opt_wine_root_dir//; # Get rid of the root directory
913     $cfile =~ s/\/\//\//g;            # Remove any double slashes
914     $cfile =~ s/^\/+//;               # Strip initial directory slash
915     $c_file = $cfile;
916   }
917   $c_file = "Implemented in \"".$c_file."\".";
918
919   # Add the implementation details
920   push (@{$comment->{TEXT}}, "IMPLEMENTATION","",$h_file,"",$c_file);
921
922   if (@$export[$EXPORT_FLAGS] & $FLAG_I386)
923   {
924     push (@{$comment->{TEXT}}, "", "Available on x86 platforms only.");
925   }
926   if (@$export[$EXPORT_FLAGS] & $FLAG_REGISTER)
927   {
928     push (@{$comment->{TEXT}}, "", "This function passes one or more arguments in registers. ",
929           "For more details, please read the source code.");
930   }
931   my $source_details = $source_files{$comment->{FILE}}[0];
932   if ($source_details->{DEBUG_CHANNEL} ne "")
933   {
934     push (@{$comment->{TEXT}}, "", "Debug channel \"".$source_details->{DEBUG_CHANNEL}."\".");
935   }
936
937   # Write out the documentation for the API
938   output_comment($comment)
939 }
940
941 # process our extra comment and output it if it is suitable.
942 sub process_extra_comment($)
943 {
944   my $comment = shift;
945
946   my $spec_details = $spec_files{$comment->{DLL_NAME}}[0];
947
948   if (!defined($spec_details))
949   {
950     if ($opt_verbose > 2)
951     {
952       print "Warning: Extra comment '".$comment->{COMMENT_NAME}."' belongs to '".
953             $comment->{DLL_NAME}."' (not passed with -w): not processing it.\n";
954     }
955     return;
956   }
957
958   # Check first to see if this is documentation for the DLL.
959   if ($comment->{COMMENT_NAME} eq $comment->{DLL_NAME})
960   {
961     if ($opt_verbose > 2)
962     {
963       print "Info: Found DLL documentation\n";
964     }
965     for (@{$comment->{TEXT}})
966     {
967       push (@{$spec_details->{DESCRIPTION}}, $_);
968     }
969     return;
970   }
971
972   # Add the comment to the DLL page as a link
973   push (@{$spec_details->{EXTRA_COMMENTS}},$comment->{COMMENT_NAME});
974
975   # If we have a prototype, process as a regular comment
976   if (@{$comment->{PROTOTYPE}})
977   {
978     $comment->{ORDINAL} = "@";
979
980     # Add an index for the comment name
981     $spec_details->{EXPORTED_NAMES}{$comment->{COMMENT_NAME}} = $spec_details->{NUM_EXPORTS};
982
983     # Add a fake exported entry
984     $spec_details->{NUM_EXPORTS}++;
985     my ($ordinal, $call_convention, $exported_name, $implementation_name, $documented) =
986      ("@", "fake", $comment->{COMMENT_NAME}, $comment->{COMMENT_NAME}, 0);
987     my @export = ($ordinal, $call_convention, $exported_name, $implementation_name, $documented);
988     push (@{$spec_details->{EXPORTS}},[@export]);
989     @{$comment->{TEXT}} = ("DESCRIPTION", @{$comment->{TEXT}});
990     process_comment($comment);
991     return;
992   }
993
994   if ($opt_verbose > 0)
995   {
996     print "Processing ",$comment->{COMMENT_NAME},"\n";
997   }
998
999   if (@{$spec_details->{CURRENT_EXTRA}})
1000   {
1001     my $current_comment = ${$spec_details->{CURRENT_EXTRA}}[0];
1002
1003     if ($opt_verbose > 0)
1004     {
1005       print "Processing old current: ",$current_comment->{COMMENT_NAME},"\n";
1006     }
1007     # Output the current comment
1008     process_comment_text($current_comment);
1009     output_open_api_file($current_comment->{COMMENT_NAME});
1010     output_api_header($current_comment);
1011     output_api_name($current_comment);
1012     output_api_comment($current_comment);
1013     output_api_footer($current_comment);
1014     output_close_api_file();
1015   }
1016
1017   if ($opt_verbose > 2)
1018   {
1019     print "Setting current to ",$comment->{COMMENT_NAME},"\n";
1020   }
1021
1022   my $comment_copy =
1023   {
1024     FILE => $comment->{FILE},
1025     COMMENT_NAME => $comment->{COMMENT_NAME},
1026     ALT_NAME => $comment->{ALT_NAME},
1027     DLL_NAME => $comment->{DLL_NAME},
1028     ORDINAL => $comment->{ORDINAL},
1029     RETURNS => $comment->{RETURNS},
1030     PROTOTYPE => [],
1031     TEXT => [],
1032   };
1033
1034   for (@{$comment->{TEXT}})
1035   {
1036     push (@{$comment_copy->{TEXT}}, $_);
1037   }
1038   # Set this comment to be the current extra comment
1039   @{$spec_details->{CURRENT_EXTRA}} = ($comment_copy);
1040 }
1041
1042 # Write a standardised comment out in the appropriate format
1043 sub output_comment($)
1044 {
1045   my $comment = shift;
1046
1047   if ($opt_verbose > 0)
1048   {
1049     print "Processing ",$comment->{COMMENT_NAME},"\n";
1050   }
1051
1052   if ($opt_verbose > 4)
1053   {
1054     print "--PROTO--\n";
1055     for (@{$comment->{PROTOTYPE}})
1056     {
1057       print "'".$_."'\n";
1058     }
1059
1060     print "--COMMENT--\n";
1061     for (@{$comment->{TEXT} })
1062     {
1063       print $_."\n";
1064     }
1065   }
1066
1067   output_open_api_file($comment->{COMMENT_NAME});
1068   output_api_header($comment);
1069   output_api_name($comment);
1070   output_api_synopsis($comment);
1071   output_api_comment($comment);
1072   output_api_footer($comment);
1073   output_close_api_file();
1074 }
1075
1076 # Write out an index file for each .spec processed
1077 sub process_index_files()
1078 {
1079   foreach my $spec_file (keys %spec_files)
1080   {
1081     my $spec_details = $spec_files{$spec_file}[0];
1082     if (defined ($spec_details->{DLL_NAME}))
1083     {
1084       if (@{$spec_details->{CURRENT_EXTRA}})
1085       {
1086         # We have an unwritten extra comment, write it
1087         my $current_comment = ${$spec_details->{CURRENT_EXTRA}}[0];
1088         process_extra_comment($current_comment);
1089         @{$spec_details->{CURRENT_EXTRA}} = ();
1090        }
1091        output_spec($spec_details);
1092     }
1093   }
1094 }
1095
1096 # Write a spec files documentation out in the appropriate format
1097 sub output_spec($)
1098 {
1099   my $spec_details = shift;
1100
1101   if ($opt_verbose > 2)
1102   {
1103     print "Writing:",$spec_details->{DLL_NAME},"\n";
1104   }
1105
1106   # Use the comment output functions for consistency
1107   my $comment =
1108   {
1109     FILE => $spec_details->{DLL_NAME},
1110     COMMENT_NAME => $spec_details->{DLL_NAME}.".".$spec_details->{DLL_EXT},
1111     ALT_NAME => $spec_details->{DLL_NAME},
1112     DLL_NAME => "",
1113     ORDINAL => "",
1114     RETURNS => "",
1115     PROTOTYPE => [],
1116     TEXT => [],
1117   };
1118   my $total_implemented = $spec_details->{NUM_FORWARDS} + $spec_details->{NUM_VARS} +
1119      $spec_details->{NUM_FUNCS};
1120   my $percent_implemented = 0;
1121   if ($total_implemented)
1122   {
1123     $percent_implemented = $total_implemented /
1124      ($total_implemented + $spec_details->{NUM_STUBS}) * 100;
1125   }
1126   $percent_implemented = int($percent_implemented);
1127   my $percent_documented = 0;
1128   if ($spec_details->{NUM_DOCS})
1129   {
1130     # Treat forwards and data as documented funcs for statistics
1131     $percent_documented = $spec_details->{NUM_DOCS} / $spec_details->{NUM_FUNCS} * 100;
1132     $percent_documented = int($percent_documented);
1133   }
1134
1135   # Make a list of the contributors to this DLL.
1136   my @contributors;
1137
1138   foreach my $source_file (keys %source_files)
1139   {
1140     my $source_details = $source_files{$source_file}[0];
1141     for (@{$source_details->{CONTRIBUTORS}})
1142     {
1143       push (@contributors, $_);
1144     }
1145   }
1146
1147   my %saw;
1148   @contributors = grep(!$saw{$_}++, @contributors); # remove dups, from perlfaq4 manpage
1149   @contributors = sort @contributors;
1150
1151   # Remove duplicates and blanks
1152   for(my $i=0; $i<@contributors; $i++)
1153   {
1154     if ($i > 0 && ($contributors[$i] =~ /$contributors[$i-1]/ || $contributors[$i-1] eq ""))
1155     {
1156       $contributors[$i-1] = $contributors[$i];
1157     }
1158   }
1159   undef %saw;
1160   @contributors = grep(!$saw{$_}++, @contributors);
1161
1162   if ($opt_verbose > 3)
1163   {
1164     print "Contributors:\n";
1165     for (@contributors)
1166     {
1167       print "'".$_."'\n";
1168     }
1169   }
1170   my $contribstring = join (", ", @contributors);
1171
1172   # Create the initial comment text
1173   @{$comment->{TEXT}} = (
1174     "NAME",
1175     $comment->{COMMENT_NAME}
1176   );
1177
1178   # Add the description, if we have one
1179   if (@{$spec_details->{DESCRIPTION}})
1180   {
1181     push (@{$comment->{TEXT}}, "DESCRIPTION");
1182     for (@{$spec_details->{DESCRIPTION}})
1183     {
1184       push (@{$comment->{TEXT}}, $_);
1185     }
1186   }
1187
1188   # Add the statistics and contributors
1189   push (@{$comment->{TEXT}},
1190     "STATISTICS",
1191     "Forwards: ".$spec_details->{NUM_FORWARDS},
1192     "Variables: ".$spec_details->{NUM_VARS},
1193     "Stubs: ".$spec_details->{NUM_STUBS},
1194     "Functions: ".$spec_details->{NUM_FUNCS},
1195     "Exports-Total: ".$spec_details->{NUM_EXPORTS},
1196     "Implemented-Total: ".$total_implemented." (".$percent_implemented."%)",
1197     "Documented-Total: ".$spec_details->{NUM_DOCS}." (".$percent_documented."%)",
1198     "CONTRIBUTORS",
1199     "The following people hold copyrights on the source files comprising this dll:",
1200     "",
1201     $contribstring,
1202     "Note: This list may not be complete.",
1203     "For a complete listing, see the Files \"AUTHORS\" and \"Changelog\" in the Wine source tree.",
1204     "",
1205   );
1206
1207   if ($opt_output_format eq "h")
1208   {
1209     # Add the exports to the comment text
1210     push (@{$comment->{TEXT}},"EXPORTS");
1211     my $exports = $spec_details->{EXPORTS};
1212     for (@$exports)
1213     {
1214       my $line = "";
1215
1216       # @$_ => ordinal, call convention, exported name, implementation name, flags;
1217       if (@$_[$EXPORT_CALL] eq "forward")
1218       {
1219         my $forward_dll = @$_[$EXPORT_IMPNAME];
1220         $forward_dll =~ s/\.(.*)//;
1221         $line = @$_[$EXPORT_EXPNAME]." (forward to ".$1."() in ".$forward_dll."())";
1222       }
1223       elsif (@$_[$EXPORT_CALL] eq "extern")
1224       {
1225         $line = @$_[$EXPORT_EXPNAME]." (extern)";
1226       }
1227       elsif (@$_[$EXPORT_CALL] eq "stub")
1228       {
1229         $line = @$_[$EXPORT_EXPNAME]." (stub)";
1230       }
1231       elsif (@$_[$EXPORT_CALL] eq "fake")
1232       {
1233         # Don't add this function here, it gets listed with the extra documentation
1234         if (!(@$_[$EXPORT_FLAGS] & $FLAG_WPAIR))
1235         {
1236           # This function should be indexed
1237           push (@index_entries_list, @$_[$EXPORT_IMPNAME].",".@$_[$EXPORT_IMPNAME]);
1238         }
1239       }
1240       elsif (@$_[$EXPORT_CALL] eq "equate" || @$_[$EXPORT_CALL] eq "variable")
1241       {
1242         $line = @$_[$EXPORT_EXPNAME]." (data)";
1243       }
1244       else
1245       {
1246         # A function
1247         if (@$_[$EXPORT_FLAGS] & $FLAG_DOCUMENTED)
1248         {
1249           # Documented
1250           $line = @$_[$EXPORT_EXPNAME]." (implemented as ".@$_[$EXPORT_IMPNAME]."())";
1251           if (@$_[$EXPORT_EXPNAME] ne @$_[$EXPORT_IMPNAME])
1252           {
1253             $line = @$_[$EXPORT_EXPNAME]." (implemented as ".@$_[$EXPORT_IMPNAME]."())";
1254           }
1255           else
1256           {
1257             $line = @$_[$EXPORT_EXPNAME]."()";
1258           }
1259           if (!(@$_[$EXPORT_FLAGS] & $FLAG_WPAIR))
1260           {
1261             # This function should be indexed
1262             push (@index_entries_list, @$_[$EXPORT_EXPNAME].",".@$_[$EXPORT_IMPNAME]);
1263           }
1264         }
1265         else
1266         {
1267           $line = @$_[$EXPORT_EXPNAME]." (not documented)";
1268         }
1269       }
1270       if ($line ne "")
1271       {
1272         push (@{$comment->{TEXT}}, $line, "");
1273       }
1274     }
1275
1276     # Add links to the extra documentation
1277     if (@{$spec_details->{EXTRA_COMMENTS}})
1278     {
1279       push (@{$comment->{TEXT}}, "SEE ALSO");
1280       my %htmp;
1281       @{$spec_details->{EXTRA_COMMENTS}} = grep(!$htmp{$_}++, @{$spec_details->{EXTRA_COMMENTS}});
1282       for (@{$spec_details->{EXTRA_COMMENTS}})
1283       {
1284         push (@{$comment->{TEXT}}, $_."()", "");
1285       }
1286     }
1287   }
1288   # The dll entry should also be indexed
1289   push (@index_entries_list, $spec_details->{DLL_NAME}.",".$spec_details->{DLL_NAME});
1290
1291   # Write out the document
1292   output_open_api_file($spec_details->{DLL_NAME});
1293   output_api_header($comment);
1294   output_api_comment($comment);
1295   output_api_footer($comment);
1296   output_close_api_file();
1297
1298   # Add this dll to the database of dll names
1299   my $output_file = $opt_output_directory."/dlls.db";
1300
1301   # Append the dllname to the output db of names
1302   open(DLLDB,">>$output_file") || die "Couldn't create $output_file\n";
1303   print DLLDB $spec_details->{DLL_NAME},"\n";
1304   close(DLLDB);
1305
1306   if ($opt_output_format eq "s")
1307   {
1308     output_sgml_dll_file($spec_details);
1309     return;
1310   }
1311
1312   if ($opt_output_format eq "x")
1313   {
1314     output_xml_dll_file($spec_details);
1315     return;
1316   }
1317
1318 }
1319
1320 #
1321 # OUTPUT FUNCTIONS
1322 # ----------------
1323 # Only these functions know anything about formatting for a specific
1324 # output type. The functions above work only with plain text.
1325 # This is to allow new types of output to be added easily.
1326
1327 # Open the api file
1328 sub output_open_api_file($)
1329 {
1330   my $output_name = shift;
1331   $output_name = $opt_output_directory."/".$output_name;
1332
1333   if ($opt_output_format eq "h")
1334   {
1335     $output_name = $output_name.".html";
1336   }
1337   elsif ($opt_output_format eq "s")
1338   {
1339     $output_name = $output_name.".sgml";
1340   }
1341   elsif ($opt_output_format eq "x")
1342   {
1343     $output_name = $output_name.".xml";
1344   }
1345   else
1346   {
1347     $output_name = $output_name.".".$opt_manual_section;
1348   }
1349   open(OUTPUT,">$output_name") || die "Couldn't create file '$output_name'\n";
1350 }
1351
1352 # Close the api file
1353 sub output_close_api_file()
1354 {
1355   close (OUTPUT);
1356 }
1357
1358 # Output the api file header
1359 sub output_api_header($)
1360 {
1361   my $comment = shift;
1362
1363   if ($opt_output_format eq "h")
1364   {
1365       print OUTPUT "<!-- Generated file - DO NOT EDIT! -->\n";
1366       print OUTPUT "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\n";
1367       print OUTPUT "<HTML>\n<HEAD>\n";
1368       print OUTPUT "<LINK REL=\"StyleSheet\" href=\"apidoc.css\" type=\"text/css\">\n";
1369       print OUTPUT "<META NAME=\"GENERATOR\" CONTENT=\"tools/c2man.pl\">\n";
1370       print OUTPUT "<META NAME=\"keywords\" CONTENT=\"Win32,Wine,API,$comment->{COMMENT_NAME}\">\n";
1371       print OUTPUT "<TITLE>Wine API: $comment->{COMMENT_NAME}</TITLE>\n</HEAD>\n<BODY>\n";
1372   }
1373   elsif ($opt_output_format eq "s" || $opt_output_format eq "x")
1374   {
1375       print OUTPUT "<!-- Generated file - DO NOT EDIT! -->\n",
1376                    "<sect1>\n",
1377                    "<title>$comment->{COMMENT_NAME}</title>\n";
1378   }
1379   else
1380   {
1381       print OUTPUT ".\\\" -*- nroff -*-\n.\\\" Generated file - DO NOT EDIT!\n".
1382                    ".TH ",$comment->{COMMENT_NAME}," ",$opt_manual_section," \"",$date,"\" \"".
1383                    "Wine API\" \"Wine API\"\n";
1384   }
1385 }
1386
1387 sub output_api_footer($)
1388 {
1389   if ($opt_output_format eq "h")
1390   {
1391       print OUTPUT "<hr><p><i class=\"copy\">Copyright &copy ".$year." The Wine Project.".
1392                    " All trademarks are the property of their respective owners.".
1393                    " Visit <a href=\"http://www.winehq.org\">WineHQ</a> for license details.".
1394                    " Generated $date.</i></p>\n</body>\n</html>\n";
1395   }
1396   elsif ($opt_output_format eq "s" || $opt_output_format eq "x")
1397   {
1398       print OUTPUT "</sect1>\n";
1399       return;
1400   }
1401   else
1402   {
1403   }
1404 }
1405
1406 sub output_api_section_start($$)
1407 {
1408   my $comment = shift;
1409   my $section_name = shift;
1410
1411   if ($opt_output_format eq "h")
1412   {
1413     print OUTPUT "\n<h2 class=\"section\">",$section_name,"</h2>\n";
1414   }
1415   elsif ($opt_output_format eq "s" || $opt_output_format eq "x")
1416   {
1417     print OUTPUT "<bridgehead>",$section_name,"</bridgehead>\n";
1418   }
1419   else
1420   {
1421     print OUTPUT "\n\.SH ",$section_name,"\n";
1422   }
1423 }
1424
1425 sub output_api_section_end()
1426 {
1427   # Not currently required by any output formats
1428 }
1429
1430 sub output_api_name($)
1431 {
1432   my $comment = shift;
1433   my $readable_name = $comment->{COMMENT_NAME};
1434   $readable_name =~ s/-/ /g; # make section names more readable
1435
1436   output_api_section_start($comment,"NAME");
1437
1438
1439   my $dll_ordinal = "";
1440   if ($comment->{ORDINAL} ne "")
1441   {
1442     $dll_ordinal = "(".$comment->{DLL_NAME}.".".$comment->{ORDINAL}.")";
1443   }
1444   if ($opt_output_format eq "h")
1445   {
1446     print OUTPUT "<p><b class=\"func_name\">",$readable_name,
1447                  "</b>&nbsp;&nbsp;<i class=\"dll_ord\">",
1448                  ,$dll_ordinal,"</i></p>\n";
1449   }
1450   elsif ($opt_output_format eq "s" || $opt_output_format eq "x")
1451   {
1452     print OUTPUT "<para>\n  <command>",$readable_name,"</command>  <emphasis>",
1453                  $dll_ordinal,"</emphasis>\n</para>\n";
1454   }
1455   else
1456   {
1457     print OUTPUT "\\fB",$readable_name,"\\fR ",$dll_ordinal;
1458   }
1459
1460   output_api_section_end();
1461 }
1462
1463 sub output_api_synopsis($)
1464 {
1465   my $comment = shift;
1466   my @fmt;
1467
1468   output_api_section_start($comment,"SYNOPSIS");
1469
1470   if ($opt_output_format eq "h")
1471   {
1472     print OUTPUT "<pre class=\"proto\">\n ", $comment->{RETURNS}," ",$comment->{COMMENT_NAME},"\n (\n";
1473     @fmt = ("", "\n", "<tt class=\"param\">", "</tt>");
1474   }
1475   elsif ($opt_output_format eq "s" || $opt_output_format eq "x")
1476   {
1477     print OUTPUT "<screen>\n ",$comment->{RETURNS}," ",$comment->{COMMENT_NAME},"\n (\n";
1478     @fmt = ("", "\n", "<emphasis>", "</emphasis>");
1479   }
1480   else
1481   {
1482     print OUTPUT $comment->{RETURNS}," ",$comment->{COMMENT_NAME},"\n (\n";
1483     @fmt = ("", "\n", "\\fI", "\\fR");
1484   }
1485
1486   # Since our prototype is output in a pre-formatted block, line up the
1487   # parameters and parameter comments in the same column.
1488
1489   # First calculate where the columns should start
1490   my $biggest_length = 0;
1491   for(my $i=0; $i < @{$comment->{PROTOTYPE}}; $i++)
1492   {
1493     my $line = ${$comment->{PROTOTYPE}}[$i];
1494     if ($line =~ /(.+?)([A-Za-z_][A-Za-z_0-9]*)$/)
1495     {
1496       my $length = length $1;
1497       if ($length > $biggest_length)
1498       {
1499         $biggest_length = $length;
1500       }
1501     }
1502   }
1503
1504   # Now pad the string with blanks
1505   for(my $i=0; $i < @{$comment->{PROTOTYPE}}; $i++)
1506   {
1507     my $line = ${$comment->{PROTOTYPE}}[$i];
1508     if ($line =~ /(.+?)([A-Za-z_][A-Za-z_0-9]*)$/)
1509     {
1510       my $pad_len = $biggest_length - length $1;
1511       my $padding = " " x ($pad_len);
1512       ${$comment->{PROTOTYPE}}[$i] = $1.$padding.$2;
1513     }
1514   }
1515
1516   for(my $i=0; $i < @{$comment->{PROTOTYPE}}; $i++)
1517   {
1518     # Format the parameter name
1519     my $line = ${$comment->{PROTOTYPE}}[$i];
1520     my $comma = ($i == @{$comment->{PROTOTYPE}}-1) ? "" : ",";
1521     $line =~ s/(.+?)([A-Za-z_][A-Za-z_0-9]*)$/  $fmt[0]$1$fmt[2]$2$fmt[3]$comma$fmt[1]/;
1522     print OUTPUT $line;
1523   }
1524
1525   if ($opt_output_format eq "h")
1526   {
1527     print OUTPUT " )\n</pre>\n";
1528   }
1529   elsif ($opt_output_format eq "s" || $opt_output_format eq "x")
1530   {
1531     print OUTPUT " )\n</screen>\n";
1532   }
1533   else
1534   {
1535     print OUTPUT " )\n";
1536   }
1537
1538   output_api_section_end();
1539 }
1540
1541 sub output_api_comment($)
1542 {
1543   my $comment = shift;
1544   my $open_paragraph = 0;
1545   my $open_raw = 0;
1546   my $param_docs = 0;
1547   my @fmt;
1548
1549   if ($opt_output_format eq "h")
1550   {
1551     @fmt = ("<p>", "</p>\n", "<tt class=\"const\">", "</tt>", "<b class=\"emp\">", "</b>",
1552             "<tt class=\"coderef\">", "</tt>", "<tt class=\"param\">", "</tt>",
1553             "<i class=\"in_out\">", "</i>", "<pre class=\"raw\">\n", "</pre>\n",
1554             "<table class=\"tab\"><colgroup><col><col><col></colgroup><tbody>\n",
1555             "</tbody></table>\n","<tr><td>","</td></tr>\n","</td>","</td><td>");
1556   }
1557   elsif ($opt_output_format eq "s" || $opt_output_format eq "x")
1558   {
1559     @fmt = ("<para>\n","\n</para>\n","<constant>","</constant>","<emphasis>","</emphasis>",
1560             "<command>","</command>","<constant>","</constant>","<emphasis>","</emphasis>",
1561             "<screen>\n","</screen>\n",
1562             "<informaltable frame=\"none\">\n<tgroup cols=\"3\">\n<tbody>\n",
1563             "</tbody>\n</tgroup>\n</informaltable>\n","<row><entry>","</entry></row>\n",
1564             "</entry>","</entry><entry>");
1565   }
1566   else
1567   {
1568     @fmt = ("\.PP\n", "\n", "\\fB", "\\fR", "\\fB", "\\fR", "\\fB", "\\fR", "\\fI", "\\fR",
1569             "\\fB", "\\fR ", "", "", "", "","","\n.PP\n","","");
1570   }
1571
1572   # Extract the parameter names
1573   my @parameter_names;
1574   for (@{$comment->{PROTOTYPE}})
1575   {
1576     if ( /(.+?)([A-Za-z_][A-Za-z_0-9]*)$/ )
1577     {
1578       push (@parameter_names, $2);
1579     }
1580   }
1581
1582   for (@{$comment->{TEXT}})
1583   {
1584     if ($opt_output_format eq "h" || $opt_output_format eq "s" || $opt_output_format eq "x")
1585     {
1586       # Map special characters
1587       s/\&/\&amp;/g;
1588       s/\</\&lt;/g;
1589       s/\>/\&gt;/g;
1590       s/\([Cc]\)/\&copy;/g;
1591       s/\(tm\)/&#174;/;
1592     }
1593
1594     if ( s/^\|// )
1595     {
1596       # Raw output
1597       if ($open_raw == 0)
1598       {
1599         if ($open_paragraph == 1)
1600         {
1601           # Close the open paragraph
1602           print OUTPUT $fmt[1];
1603           $open_paragraph = 0;
1604         }
1605         # Start raw output
1606         print OUTPUT $fmt[12];
1607         $open_raw = 1;
1608       }
1609       if ($opt_output_format eq "")
1610       {
1611         print OUTPUT ".br\n"; # Prevent 'man' running these lines together
1612       }
1613       print OUTPUT $_,"\n";
1614     }
1615     else
1616     {
1617       if ($opt_output_format eq "h")
1618       {
1619         # Link to the file in WineHQ cvs
1620         s/^(Implemented in \")(.+?)(\"\.)/$1$2$3 http:\/\/source.winehq.org\/source\/$2/g;
1621         s/^(Declared in \")(.+?)(\"\.)/$1$2$3 http:\/\/source.winehq.org\/source\/include\/$2/g;
1622       }
1623       # Highlight strings
1624       s/(\".+?\")/$fmt[2]$1$fmt[3]/g;
1625       # Highlight literal chars
1626       s/(\'.\')/$fmt[2]$1$fmt[3]/g;
1627       s/(\'.{2}\')/$fmt[2]$1$fmt[3]/g;
1628       # Highlight numeric constants
1629       s/( |\-|\+|\.|\()([0-9\-\.]+)( |\-|$|\.|\,|\*|\?|\))/$1$fmt[2]$2$fmt[3]$3/g;
1630
1631       # Leading cases ("xxxx:","-") start new paragraphs & are emphasised
1632       # FIXME: Using bullet points for leading '-' would look nicer.
1633       if ($open_paragraph == 1 && $param_docs == 0)
1634       {
1635         s/^(\-)/$fmt[1]$fmt[0]$fmt[4]$1$fmt[5]/;
1636         s/^([[A-Za-z\-]+\:)/$fmt[1]$fmt[0]$fmt[4]$1$fmt[5]/;
1637       }
1638       else
1639       {
1640         s/^(\-)/$fmt[4]$1$fmt[5]/;
1641         s/^([[A-Za-z\-]+\:)/$fmt[4]$1$fmt[5]/;
1642       }
1643
1644       if ($opt_output_format eq "h")
1645       {
1646         # Html uses links for API calls
1647         while ( /([A-Za-z_]+[A-Za-z_0-9-]+)(\(\))/)
1648         {
1649           my $link = $1;
1650           my $readable_link = $1;
1651           $readable_link =~ s/-/ /g;
1652
1653           s/([A-Za-z_]+[A-Za-z_0-9-]+)(\(\))/<a href\=\"$link\.html\">$readable_link<\/a>/;
1654         }
1655         # Index references
1656         s/\{\{(.*?)\}\}\{\{(.*?)\}\}/<a href\=\"$2\.html\">$1<\/a>/g;
1657         s/ ([A-Z_])(\(\))/<a href\=\"$1\.html\">$1<\/a>/g;
1658         # And references to COM objects (hey, they'll get documented one day)
1659         s/ (I[A-Z]{1}[A-Za-z0-9_]+) (Object|object|Interface|interface)/ <a href\=\"$1\.html\">$1<\/a> $2/g;
1660         # Convert any web addresses to real links
1661         s/(http\:\/\/)(.+?)($| )/<a href\=\"$1$2\">$2<\/a>$3/g;
1662       }
1663       else
1664       {
1665         if ($opt_output_format eq "")
1666         {
1667           # Give the man section for API calls
1668           s/ ([A-Za-z_]+[A-Za-z_0-9-]+)\(\)/ $fmt[6]$1\($opt_manual_section\)$fmt[7]/g;
1669         }
1670         else
1671         {
1672           # Highlight API calls
1673           s/ ([A-Za-z_]+[A-Za-z_0-9-]+\(\))/ $fmt[6]$1$fmt[7]/g;
1674         }
1675
1676         # And references to COM objects
1677         s/ (I[A-Z]{1}[A-Za-z0-9_]+) (Object|object|Interface|interface)/ $fmt[6]$1$fmt[7] $2/g;
1678       }
1679
1680       if ($open_raw == 1)
1681       {
1682         # Finish the raw output
1683         print OUTPUT $fmt[13];
1684         $open_raw = 0;
1685       }
1686
1687       if ( /^[A-Z]+$/ || /^SEE ALSO$/ )
1688       {
1689         # Start of a new section
1690         if ($open_paragraph == 1)
1691         {
1692           if ($param_docs == 1)
1693           {
1694             print OUTPUT $fmt[17],$fmt[15];
1695             $param_docs = 0;
1696           }
1697           else
1698           {
1699             print OUTPUT $fmt[1];
1700           }
1701           $open_paragraph = 0;
1702         }
1703         output_api_section_start($comment,$_);
1704         if ( /^PARAMS$/ || /^MEMBERS$/ )
1705         {
1706           print OUTPUT $fmt[14];
1707           $param_docs = 1;
1708         }
1709         else
1710         {
1711           #print OUTPUT $fmt[15];
1712           #$param_docs = 0;
1713         }
1714       }
1715       elsif ( /^$/ )
1716       {
1717         # Empty line, indicating a new paragraph
1718         if ($open_paragraph == 1)
1719         {
1720           if ($param_docs == 0)
1721           {
1722             print OUTPUT $fmt[1];
1723             $open_paragraph = 0;
1724           }
1725         }
1726       }
1727       else
1728       {
1729         if ($param_docs == 1)
1730         {
1731           if ($open_paragraph == 1)
1732           {
1733             # For parameter docs, put each parameter into a new paragraph/table row
1734             print OUTPUT $fmt[17];
1735             $open_paragraph = 0;
1736           }
1737           s/(\[.+\])( *)/$fmt[19]$fmt[10]$1$fmt[11]$fmt[19] /; # Format In/Out
1738         }
1739         else
1740         {
1741           # Within paragraph lines, prevent lines running together
1742           $_ = $_." ";
1743         }
1744
1745         # Format parameter names where they appear in the comment
1746         for my $parameter_name (@parameter_names)
1747         {
1748           s/(^|[ \.\,\(\-\*])($parameter_name)($|[ \.\)\,\-\/]|(\=[^"]))/$1$fmt[8]$2$fmt[9]$3/g;
1749         }
1750         # Structure dereferences include the dereferenced member
1751         s/(\-\>[A-Za-z_]+)/$fmt[8]$1$fmt[9]/g;
1752         s/(\-\&gt\;[A-Za-z_]+)/$fmt[8]$1$fmt[9]/g;
1753
1754         if ($open_paragraph == 0)
1755         {
1756           if ($param_docs == 1)
1757           {
1758             print OUTPUT $fmt[16];
1759           }
1760           else
1761           {
1762             print OUTPUT $fmt[0];
1763           }
1764           $open_paragraph = 1;
1765         }
1766         # Anything in all uppercase on its own gets emphasised
1767         s/(^|[ \.\,\(\[\|\=])([A-Z]+?[A-Z0-9_]+)($|[ \.\,\*\?\|\)\=\'])/$1$fmt[6]$2$fmt[7]$3/g;
1768
1769         print OUTPUT $_;
1770       }
1771     }
1772   }
1773   if ($open_raw == 1)
1774   {
1775     print OUTPUT $fmt[13];
1776   }
1777   if ($param_docs == 1 && $open_paragraph == 1)
1778   {
1779     print OUTPUT $fmt[17];
1780     $open_paragraph = 0;
1781   }
1782   if ($param_docs == 1)
1783   {
1784     print OUTPUT $fmt[15];
1785   }
1786   if ($open_paragraph == 1)
1787   {
1788     print OUTPUT $fmt[1];
1789   }
1790 }
1791
1792 # Create the master index file
1793 sub output_master_index_files()
1794 {
1795   if ($opt_output_format eq "")
1796   {
1797     return; # No master index for man pages
1798   }
1799
1800   if ($opt_output_format eq "h")
1801   {
1802     # Append the index entries to the output db of index entries
1803     my $output_file = $opt_output_directory."/index.db";
1804     open(INDEXDB,">>$output_file") || die "Couldn't create $output_file\n";
1805     for (@index_entries_list)
1806     {
1807       $_ =~ s/A\,/\,/;
1808       print INDEXDB $_."\n";
1809     }
1810     close(INDEXDB);
1811   }
1812
1813   # Use the comment output functions for consistency
1814   my $comment =
1815   {
1816     FILE => "",
1817     COMMENT_NAME => "The Wine API Guide",
1818     ALT_NAME => "The Wine API Guide",
1819     DLL_NAME => "",
1820     ORDINAL => "",
1821     RETURNS => "",
1822     PROTOTYPE => [],
1823     TEXT => [],
1824   };
1825
1826   if ($opt_output_format eq "s" || $opt_output_format eq "x")
1827   {
1828     $comment->{COMMENT_NAME} = "Introduction";
1829     $comment->{ALT_NAME} = "Introduction",
1830   }
1831   elsif ($opt_output_format eq "h")
1832   {
1833     @{$comment->{TEXT}} = (
1834       "NAME",
1835        $comment->{COMMENT_NAME},
1836        "INTRODUCTION",
1837     );
1838   }
1839
1840   # Create the initial comment text
1841   push (@{$comment->{TEXT}},
1842     "This document describes the API calls made available",
1843     "by Wine. They are grouped by the dll that exports them.",
1844     "",
1845     "Please do not edit this document, since it is generated automatically",
1846     "from the Wine source code tree. Details on updating this documentation",
1847     "are given in the \"Wine Developers Guide\".",
1848     "CONTRIBUTORS",
1849     "API documentation is generally written by the person who ",
1850     "implements a given API call. Authors of each dll are listed in the overview ",
1851     "section for that dll. Additional contributors who have updated source files ",
1852     "but have not entered their names in a copyright statement are noted by an ",
1853     "entry in the file \"Changelog\" from the Wine source code distribution.",
1854       ""
1855   );
1856
1857   # Read in all dlls from the database of dll names
1858   my $input_file = $opt_output_directory."/dlls.db";
1859   my @dlls = `cat $input_file|sort|uniq`;
1860
1861   if ($opt_output_format eq "h")
1862   {
1863     # HTML gets a list of all the dlls and an index. For docbook the index creates this for us
1864     push (@{$comment->{TEXT}},
1865       "INDEX",
1866       "For an alphabetical listing of the functions available, please click the ",
1867       "first letter of the functions name below:","",
1868       "[ _(), A(), B(), C(), D(), E(), F(), G(), H(), ".
1869       "I(), J(), K(), L(), M(), N(), O(), P(), Q(), ".
1870       "R(), S(), T(), U(), V(), W(), X(), Y(), Z() ]", "",
1871       "DLLS",
1872       "Each dll provided by Wine is documented individually. The following dlls are provided :",
1873       ""
1874     );
1875     # Add the dlls to the comment
1876     for (@dlls)
1877     {
1878       $_ =~ s/(\..*)?\n/\(\)/;
1879       push (@{$comment->{TEXT}}, $_, "");
1880     }
1881     output_open_api_file("index");
1882   }
1883   elsif ($opt_output_format eq "s" || $opt_output_format eq "x")
1884   {
1885     # Just write this as the initial blurb, with a chapter heading
1886     output_open_api_file("blurb");
1887     print OUTPUT "<chapter id =\"blurb\">\n<title>Introduction to The Wine API Guide</title>\n"
1888   }
1889
1890   # Write out the document
1891   output_api_header($comment);
1892   output_api_comment($comment);
1893   output_api_footer($comment);
1894   if ($opt_output_format eq "s" || $opt_output_format eq "x")
1895   {
1896     print OUTPUT "</chapter>\n" # finish the chapter
1897   }
1898   output_close_api_file();
1899
1900   if ($opt_output_format eq "s")
1901   {
1902     output_sgml_master_file(\@dlls);
1903     return;
1904   }
1905   if ($opt_output_format eq "x")
1906   {
1907     output_xml_master_file(\@dlls);
1908     return;
1909   }
1910   if ($opt_output_format eq "h")
1911   {
1912     output_html_index_files();
1913     output_html_stylesheet();
1914     return;
1915   }
1916 }
1917
1918 # Write the master wine-api.xml, linking it to each dll.
1919 sub output_xml_master_file($)
1920 {
1921   my $dlls = shift;
1922
1923   output_open_api_file("wine-api");
1924   print OUTPUT "<?xml version='1.0'?>";
1925   print OUTPUT "<!-- Generated file - DO NOT EDIT! -->\n";
1926   print OUTPUT "<!DOCTYPE book PUBLIC \"-//OASIS//DTD DocBook V5.0/EN\" ";
1927   print OUTPUT "               \"http://www.docbook.org/xml/5.0/dtd/docbook.dtd\" [\n\n";
1928   print OUTPUT "<!ENTITY blurb SYSTEM \"blurb.xml\">\n";
1929
1930   # List the entities
1931   for (@$dlls)
1932   {
1933     $_ =~ s/(\..*)?\n//;
1934     print OUTPUT "<!ENTITY ",$_," SYSTEM \"",$_,".xml\">\n"
1935   }
1936
1937   print OUTPUT "]>\n\n<book id=\"index\">\n<bookinfo><title>The Wine API Guide</title></bookinfo>\n\n";
1938   print OUTPUT "  &blurb;\n";
1939
1940   for (@$dlls)
1941   {
1942     print OUTPUT "  &",$_,";\n"
1943   }
1944   print OUTPUT "\n\n</book>\n";
1945
1946   output_close_api_file();
1947 }
1948
1949 # Write the master wine-api.sgml, linking it to each dll.
1950 sub output_sgml_master_file($)
1951 {
1952   my $dlls = shift;
1953
1954   output_open_api_file("wine-api");
1955   print OUTPUT "<!-- Generated file - DO NOT EDIT! -->\n";
1956   print OUTPUT "<!doctype book PUBLIC \"-//OASIS//DTD DocBook V3.1//EN\" [\n\n";
1957   print OUTPUT "<!entity blurb SYSTEM \"blurb.sgml\">\n";
1958
1959   # List the entities
1960   for (@$dlls)
1961   {
1962     $_ =~ s/(\..*)?\n//;
1963     print OUTPUT "<!entity ",$_," SYSTEM \"",$_,".sgml\">\n"
1964   }
1965
1966   print OUTPUT "]>\n\n<book id=\"index\">\n<bookinfo><title>The Wine API Guide</title></bookinfo>\n\n";
1967   print OUTPUT "  &blurb;\n";
1968
1969   for (@$dlls)
1970   {
1971     print OUTPUT "  &",$_,";\n"
1972   }
1973   print OUTPUT "\n\n</book>\n";
1974
1975   output_close_api_file();
1976 }
1977
1978 # Produce the sgml for the dll chapter from the generated files
1979 sub output_sgml_dll_file($)
1980 {
1981   my $spec_details = shift;
1982
1983   # Make a list of all the documentation files to include
1984   my $exports = $spec_details->{EXPORTS};
1985   my @source_files = ();
1986   for (@$exports)
1987   {
1988     # @$_ => ordinal, call convention, exported name, implementation name, documented;
1989     if (@$_[$EXPORT_CALL] ne "forward" && @$_[$EXPORT_CALL] ne "extern" &&
1990         @$_[$EXPORT_CALL] ne "stub" && @$_[$EXPORT_CALL] ne "equate" &&
1991         @$_[$EXPORT_CALL] ne "variable" && @$_[$EXPORT_CALL] ne "fake" &&
1992         @$_[$EXPORT_FLAGS] & $FLAG_DOCUMENTED)
1993     {
1994       # A documented function
1995       push (@source_files,@$_[$EXPORT_IMPNAME]);
1996     }
1997   }
1998
1999   push (@source_files,@{$spec_details->{EXTRA_COMMENTS}});
2000
2001   @source_files = sort @source_files;
2002
2003   # create a new chapter for this dll
2004   my $tmp_name = $opt_output_directory."/".$spec_details->{DLL_NAME}.".tmp";
2005   open(OUTPUT,">$tmp_name") || die "Couldn't create $tmp_name\n";
2006   print OUTPUT "<chapter>\n<title>$spec_details->{DLL_NAME}</title>\n";
2007   output_close_api_file();
2008
2009   # Add the sorted documentation, cleaning up as we go
2010   `cat $opt_output_directory/$spec_details->{DLL_NAME}.sgml >>$tmp_name`;
2011   for (@source_files)
2012   {
2013     `cat $opt_output_directory/$_.sgml >>$tmp_name`;
2014     `rm -f $opt_output_directory/$_.sgml`;
2015   }
2016
2017   # close the chapter, and overwite the dll source
2018   open(OUTPUT,">>$tmp_name") || die "Couldn't create $tmp_name\n";
2019   print OUTPUT "</chapter>\n";
2020   close OUTPUT;
2021   `mv $tmp_name $opt_output_directory/$spec_details->{DLL_NAME}.sgml`;
2022 }
2023
2024 # Produce the xml for the dll chapter from the generated files
2025 sub output_xml_dll_file($)
2026 {
2027   my $spec_details = shift;
2028
2029   # Make a list of all the documentation files to include
2030   my $exports = $spec_details->{EXPORTS};
2031   my @source_files = ();
2032   for (@$exports)
2033   {
2034     # @$_ => ordinal, call convention, exported name, implementation name, documented;
2035     if (@$_[$EXPORT_CALL] ne "forward" && @$_[$EXPORT_CALL] ne "extern" &&
2036         @$_[$EXPORT_CALL] ne "stub" && @$_[$EXPORT_CALL] ne "equate" &&
2037         @$_[$EXPORT_CALL] ne "variable" && @$_[$EXPORT_CALL] ne "fake" &&
2038         @$_[$EXPORT_FLAGS] & $FLAG_DOCUMENTED)
2039     {
2040       # A documented function
2041       push (@source_files,@$_[$EXPORT_IMPNAME]);
2042     }
2043   }
2044
2045   push (@source_files,@{$spec_details->{EXTRA_COMMENTS}});
2046
2047   @source_files = sort @source_files;
2048
2049   # create a new chapter for this dll
2050   my $tmp_name = $opt_output_directory."/".$spec_details->{DLL_NAME}.".tmp";
2051   open(OUTPUT,">$tmp_name") || die "Couldn't create $tmp_name\n";
2052   print OUTPUT "<?xml version='1.0' encoding='UTF-8'?>\n<chapter>\n<title>$spec_details->{DLL_NAME}</title>\n";
2053   output_close_api_file();
2054
2055   # Add the sorted documentation, cleaning up as we go
2056   `cat $opt_output_directory/$spec_details->{DLL_NAME}.xml >>$tmp_name`;
2057   for (@source_files)
2058   {
2059     `cat $opt_output_directory/$_.xml >>$tmp_name`;
2060     `rm -f $opt_output_directory/$_.xml`;
2061   }
2062
2063   # close the chapter, and overwite the dll source
2064   open(OUTPUT,">>$tmp_name") || die "Couldn't create $tmp_name\n";
2065   print OUTPUT "</chapter>\n";
2066   close OUTPUT;
2067   `mv $tmp_name $opt_output_directory/$spec_details->{DLL_NAME}.xml`;
2068 }
2069
2070 # Write the html index files containing the function names
2071 sub output_html_index_files()
2072 {
2073   if ($opt_output_format ne "h")
2074   {
2075     return;
2076   }
2077
2078   my @letters = ('_', 'A' .. 'Z');
2079
2080   # Read in all functions
2081   my $input_file = $opt_output_directory."/index.db";
2082   my @funcs = `cat $input_file|sort|uniq`;
2083
2084   for (@letters)
2085   {
2086     my $letter = $_;
2087     my $comment =
2088     {
2089       FILE => "",
2090       COMMENT_NAME => "",
2091       ALT_NAME => "",
2092       DLL_NAME => "",
2093       ORDINAL => "",
2094       RETURNS => "",
2095       PROTOTYPE => [],
2096       TEXT => [],
2097     };
2098
2099     $comment->{COMMENT_NAME} = $letter." Functions";
2100     $comment->{ALT_NAME} = $letter." Functions";
2101
2102     push (@{$comment->{TEXT}},
2103       "NAME",
2104       $comment->{COMMENT_NAME},
2105       "FUNCTIONS"
2106     );
2107
2108     # Add the functions to the comment
2109     for (@funcs)
2110     {
2111       my $first_char = substr ($_, 0, 1);
2112       $first_char = uc $first_char;
2113
2114       if ($first_char eq $letter)
2115       {
2116         my $name = $_;
2117         my $file;
2118         $name =~ s/(^.*?)\,(.*?)\n/$1/;
2119         $file = $2;
2120         push (@{$comment->{TEXT}}, "{{".$name."}}{{".$file."}}","");
2121       }
2122     }
2123
2124     # Write out the document
2125     output_open_api_file($letter);
2126     output_api_header($comment);
2127     output_api_comment($comment);
2128     output_api_footer($comment);
2129     output_close_api_file();
2130   }
2131 }
2132
2133 # Output the stylesheet for HTML output
2134 sub output_html_stylesheet()
2135 {
2136   if ($opt_output_format ne "h")
2137   {
2138     return;
2139   }
2140
2141   my $css;
2142   ($css = <<HERE_TARGET) =~ s/^\s+//gm;
2143 /*
2144  * Default styles for Wine HTML Documentation.
2145  *
2146  * This style sheet should be altered to suit your needs/taste.
2147  */
2148 BODY { /* Page body */
2149 background-color: white;
2150 color: black;
2151 font-family: Tahoma,sans-serif;
2152 font-style: normal;
2153 font-size: 10pt;
2154 }
2155 a:link { color: #4444ff; } /* Links */
2156 a:visited { color: #333377 }
2157 a:active { color: #0000dd }
2158 H2.section { /* Section Headers */
2159 font-family: sans-serif;
2160 color: #777777;
2161 background-color: #F0F0FE;
2162 margin-left: 0.2in;
2163 margin-right: 1.0in;
2164 }
2165 b.func_name { /* Function Name */
2166 font-size: 10pt;
2167 font-style: bold;
2168 }
2169 i.dll_ord { /* Italicised DLL+ordinal */
2170 color: #888888;
2171 font-family: sans-serif;
2172 font-size: 8pt;
2173 }
2174 p { /* Paragraphs */
2175 margin-left: 0.5in;
2176 margin-right: 0.5in;
2177 }
2178 table { /* tables */
2179 margin-left: 0.5in;
2180 margin-right: 0.5in;
2181 }
2182 pre.proto /* API Function prototype */
2183 {
2184 border-style: solid;
2185 border-width: 1px;
2186 border-color: #777777;
2187 background-color: #F0F0BB;
2188 color: black;
2189 font-size: 10pt;
2190 vertical-align: top;
2191 margin-left: 0.5in;
2192 margin-right: 1.0in;
2193 }
2194 pre.raw { /* Raw text output */
2195 margin-left: 0.6in;
2196 margin-right: 1.1in;
2197 background-color: #8080DC;
2198 }
2199 tt.param { /* Parameter name */
2200 font-style: italic;
2201 color: blue;
2202 }
2203 tt.const { /* Constant */
2204 color: red;
2205 }
2206 i.in_out { /* In/Out */
2207 font-size: 8pt;
2208 color: grey;
2209 }
2210 tt.coderef { /* Code in description text */
2211 color: darkgreen;
2212 }
2213 b.emp /* Emphasis */ {
2214 font-style: bold;
2215 color: darkblue;
2216 }
2217 i.footer { /* Footer */
2218 font-family: sans-serif;
2219 font-size: 6pt;
2220 color: darkgrey;
2221 }
2222 HERE_TARGET
2223
2224   my $output_file = "$opt_output_directory/apidoc.css";
2225   open(CSS,">$output_file") || die "Couldn't create the file $output_file\n";
2226   print CSS $css;
2227   close(CSS);
2228 }
2229
2230
2231 sub usage()
2232 {
2233   print "\nCreate API Documentation from Wine source code.\n\n",
2234         "Usage: c2man.pl [options] {-w <spec>} {-I <include>} {<source>}\n",
2235         "Where: <spec> is a .spec file giving a DLL's exports.\n",
2236         "       <include> is an include directory used by the DLL.\n",
2237         "       <source> is a source file of the DLL.\n",
2238         "       The above can be given multiple times on the command line, as appropriate.\n",
2239         "Options:\n",
2240         " -Th      : Output HTML instead of a man page\n",
2241         " -Ts      : Output SGML (DocBook source) instead of a man page\n",
2242         " -C <dir> : Source directory, to find source files if they are not found in the\n",
2243         "            current directory. Default is \"",$opt_source_dir,"\"\n",
2244         " -R <dir> : Root of build directory, default is \"",$opt_wine_root_dir,"\"\n",
2245         " -o <dir> : Create output in <dir>, default is \"",$opt_output_directory,"\"\n",
2246         " -s <sect>: Set manual section to <sect>, default is ",$opt_manual_section,"\n",
2247         " -e       : Output \"FIXME\" documentation from empty comments.\n",
2248         " -v       : Verbosity. Can be given more than once for more detail.\n";
2249 }
2250
2251
2252 #
2253 # Main
2254 #
2255
2256 # Print usage if we're called with no args
2257 if( @ARGV == 0)
2258 {
2259   usage();
2260 }
2261
2262 # Process command line options
2263 while(defined($_ = shift @ARGV))
2264 {
2265   if( s/^-// )
2266   {
2267     # An option.
2268     for ($_)
2269     {
2270       /^o$/  && do { $opt_output_directory = shift @ARGV; last; };
2271       s/^S// && do { $opt_manual_section   = $_;          last; };
2272       /^Th$/ && do { $opt_output_format  = "h";           last; };
2273       /^Ts$/ && do { $opt_output_format  = "s";           last; };
2274       /^Tx$/ && do { $opt_output_format  = "x";           last; };
2275       /^v$/  && do { $opt_verbose++;                      last; };
2276       /^e$/  && do { $opt_output_empty = 1;               last; };
2277       /^L$/  && do { last; };
2278       /^w$/  && do { @opt_spec_file_list = (@opt_spec_file_list, shift @ARGV); last; };
2279       s/^I// && do { if ($_ ne ".") {
2280                        foreach my $include (`find $_/./ -type d ! -name tests`) {
2281                          $include =~ s/\n//;
2282                          $include = $include."/*.h";
2283                          $include =~ s/\/\//\//g;
2284                          my $have_headers = `ls $include >/dev/null 2>&1`;
2285                          if ($? >> 8 == 0) { @opt_header_file_list = (@opt_header_file_list, $include); }
2286                        };
2287                      }
2288                      last;
2289                    };
2290       s/^C// && do {
2291                      if ($_ ne "") { $opt_source_dir = $_; }
2292                      last;
2293                    };
2294       s/^R// && do { if ($_ =~ /^\//) { $opt_wine_root_dir = $_; }
2295                      else { $opt_wine_root_dir = `cd $pwd/$_ && pwd`; }
2296                      $opt_wine_root_dir =~ s/\n//;
2297                      $opt_wine_root_dir =~ s/\/\//\//g;
2298                      if (! $opt_wine_root_dir =~ /\/$/ ) { $opt_wine_root_dir = $opt_wine_root_dir."/"; };
2299                      last;
2300              };
2301       die "Unrecognised option $_\n";
2302     }
2303   }
2304   else
2305   {
2306     # A source file.
2307     push (@opt_source_file_list, $_);
2308   }
2309 }
2310
2311 # Remove duplicate include directories
2312 my %htmp;
2313 @opt_header_file_list = grep(!$htmp{$_}++, @opt_header_file_list);
2314
2315 if ($opt_verbose > 3)
2316 {
2317   print "Output dir:'".$opt_output_directory."'\n";
2318   print "Section   :'".$opt_manual_section."'\n";
2319   print "Format    :'".$opt_output_format."'\n";
2320   print "Source dir:'".$opt_source_dir."'\n";
2321   print "Root      :'".$opt_wine_root_dir."'\n";
2322   print "Spec files:'@opt_spec_file_list'\n";
2323   print "Includes  :'@opt_header_file_list'\n";
2324   print "Sources   :'@opt_source_file_list'\n";
2325 }
2326
2327 if (@opt_spec_file_list == 0)
2328 {
2329   exit 0; # Don't bother processing non-dll files
2330 }
2331
2332 # Make sure the output directory exists
2333 unless (-d $opt_output_directory)
2334 {
2335     mkdir $opt_output_directory or die "Cannot create directory $opt_output_directory\n";
2336 }
2337
2338 # Read in each .spec files exports and other details
2339 while(my $spec_file = shift @opt_spec_file_list)
2340 {
2341   process_spec_file($spec_file);
2342 }
2343
2344 if ($opt_verbose > 3)
2345 {
2346     foreach my $spec_file ( keys %spec_files )
2347     {
2348         print "in '$spec_file':\n";
2349         my $spec_details = $spec_files{$spec_file}[0];
2350         my $exports = $spec_details->{EXPORTS};
2351         for (@$exports)
2352         {
2353            print @$_[$EXPORT_ORDINAL].",".@$_[$EXPORT_CALL].", ".
2354                  @$_[$EXPORT_EXPNAME].",".@$_[$EXPORT_IMPNAME]."\n";
2355         }
2356     }
2357 }
2358
2359 # Extract and output the comments from each source file
2360 while(defined($_ = shift @opt_source_file_list))
2361 {
2362   process_source_file($_);
2363 }
2364
2365 # Write the index files for each spec
2366 process_index_files();
2367
2368 # Write the master index file
2369 output_master_index_files();
2370
2371 exit 0;