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