Adding stuff for man page generation so toolset is consistent.
[privoxy.git] / utils / docbook2man / docbook2man-spec.pl
1 =head1 NAME
2
3 docbook2man-spec - convert DocBook RefEntries to Unix manpages
4
5 =head1 SYNOPSIS
6
7 The SGMLSpm package from CPAN.  This contains the sgmlspl script which
8 is used to grok this file.  Use it like this:
9
10 nsgmls some-docbook-document.sgml | sgmlspl docbook2man-spec.pl
11
12 =head1 DESCRIPTION
13
14 This is a sgmlspl spec file that produces Unix-style
15 manpages from RefEntry markup.
16
17 See the accompanying RefEntry man page for 'plain new' documentation. :)
18
19 =head1 LIMITATIONS
20
21 Trying docbook2man on non-DocBook or non-conformant SGML results in
22 undefined behavior. :-)
23
24 This program is a slow, dodgy Perl script.
25
26 This program does not come close to supporting all the possible markup
27 in DocBook, and will produce wrong output in some cases with supported
28 markup.
29
30 =head1 TODO
31
32 Add new element handling and fix existing handling.  Be robust.
33 Produce cleanest, readable man output as possible (unlike some
34 other converters).  Follow Linux man(7) convention.
35 If this results in added logic in this script,
36 that's okay.  The code should still be reasonably organized.
37
38 Make it faster.  If Perl sucks port it to another language.
39
40 =head1 COPYRIGHT
41
42 Copyright (C) 1998-1999 Steve Cheng <steve@ggi-project.org>
43
44 This program is free software; you can redistribute it and/or modify it
45 under the terms of the GNU General Public License as published by the Free
46 Software Foundation; either version 2, or (at your option) any later
47 version.
48
49 You should have received a copy of the GNU General Public License along with
50 this program; see the file COPYING.  If not, please write to the Free
51 Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
52
53 =cut
54
55 # $Id: docbook2man-spec.pl,v 1.1.2.1 2002/08/11 20:01:30 hal9 Exp $
56
57 use SGMLS;                      # Use the SGMLS package.
58 use SGMLS::Output;              # Use stack-based output.
59 use SGMLS::Refs;
60
61 ########################################################################
62 # SGMLSPL script produced automatically by the script sgmlspl.pl
63 #
64 # Document Type: any, but processes only RefEntries
65 # Edited by: me :)
66 ########################################################################
67
68 $write_manpages = 0;
69 $blank_xrefs = 0;
70
71 sgml('start', sub { 
72         push_output('nul');
73         $raw_cdata = 1;                 # Makes it a bit faster.
74         
75         # Links file
76         open(LINKSFILE, ">manpage.links");
77
78         $Refs = new SGMLS::Refs("manpage.refs");
79 });
80 sgml('end', sub {
81         close(LINKSFILE);
82         if($blank_xrefs) {
83                 print STDERR "Warning: output contains unresolved XRefs\n";
84         }
85 });
86
87
88
89
90 ########################################################################
91 #
92 # Output helpers 
93 #
94 ########################################################################
95
96 # Our own version of sgml() and output() to allow simple string output
97 # to play well with roff's stupid whitespace rules. 
98
99 sub man_sgml
100 {
101         if(ref($_[1]) eq 'CODE') {
102                 return &sgml;
103         }
104         
105         my $s = $_[1];
106
107         $s =~ s/\\/\\\\/g;
108         $s =~ s/'/\\'/g;
109
110         # \n at the beginning means start at beginning of line
111         if($s =~ s/^\n//) {
112                 $sub = 'sub { output "\n" unless $newline_last++; ';
113                 if($s eq '') { 
114                         sgml($_[0], eval('sub { output "\n" unless $newline_last++; }'));
115                 } elsif($s =~ /\n$/) {
116                         sgml($_[0], eval("sub { output \"\\n\" unless \$newline_last++; output '$s'; }"));
117                 } else {
118                         sgml($_[0], eval("sub { output \"\\n\" unless \$newline_last; output '$s'; \$newline_last = 0; }"));
119                 }
120         } else {
121                 if($s =~ /\n$/) {
122                         sgml($_[0], eval("sub { output '$s'; \$newline_last = 1; }"));
123                 } else {
124                         sgml($_[0], eval("sub { output '$s'; \$newline_last = 0; }"));
125                 }
126         }
127 }
128
129 sub man_output
130 {
131         $_ = shift;
132         if(s/^\n//) {
133                 output "\n" unless $newline_last++;
134         }
135         return if $_ eq '';
136         
137         output $_;
138
139         if(@_) {
140                 output @_;
141                 $newline_last = (pop(@_) =~ /\n$/);
142         } else {
143                 $newline_last = ($_ =~ /\n$/)
144         }
145 }
146
147 # Fold lines into one, quote some characters
148 sub fold_string
149 {
150         $_ = shift;
151         
152         s/\\/\\\\/g;
153         s/"/\\\&"/g;
154
155         # Change tabs to spaces
156         tr/\t\n/  /;
157
158         # Trim whitespace from beginning and end.
159         s/^ +//;
160         s/ +$//;
161
162         return $_;
163 }
164         
165 sub save_cdata()
166 {
167         $raw_cdata++;
168         push_output('string');
169 }
170
171 sub bold_on()
172 {
173         # If the last font is also bold, don't change anything.
174         # Basically this is to just get more readable man output.
175         if($fontstack[$#fontstack] ne 'bold') {
176                 if(!$raw_cdata) {
177                         output '\fB';
178                         $newline_last = 0;
179                 }
180         }
181         push(@fontstack, 'bold');
182 }
183
184 sub italic_on()
185 {
186         # If the last font is also italic, don't change anything.
187         if($fontstack[$#fontstack] ne 'italic') {
188                 if(!$raw_cdata) {
189                         output '\fI';
190                         $newline_last = 0;
191                 }
192         }
193         push(@fontstack, 'italic');
194 }
195
196 sub font_off()
197 {
198         my $thisfont = pop(@fontstack);
199         my $lastfont = $fontstack[$#fontstack];
200         
201         # Only output font change if it is different
202         if($thisfont ne $lastfont) {
203                 if($raw_cdata)                  { return; }
204                 elsif($lastfont eq 'bold')      { output '\fB'; }
205                 elsif($lastfont eq 'italic')    { output '\fI'; }
206                 else                            { output '\fR'; }
207         
208                 $newline_last = 0;
209         }
210 }
211
212
213
214
215
216
217 ########################################################################
218 #
219 # Manpage management
220 #
221 ########################################################################
222
223 sgml('<REFENTRY>', sub { 
224         # This will be overwritten at end of REFMETA, when we know the name of the page.
225         pop_output();
226         
227         $write_manpages = 1;            # Currently writing manpage.
228         
229         $nocollapse_whitespace = 0;     # Current whitespace collapse counter.
230         $newline_last = 1;              # At beginning of line?
231                 # Just a bit of warning, you will see this variable manipulated
232                 # manually a lot.  It makes the code harder to follow but it
233                 # saves you from having to worry about collapsing at the end of
234                 # parse, stopping at verbatims, etc.
235         $raw_cdata = 0;                 # Instructs certain output functions to
236                                         # leave CDATA alone, so we can assign
237                                         # it to a string and process it, etc.
238         @fontstack = ();                # Fonts being activated.
239         
240         $manpage_title = '';            # Needed for indexing.
241         $manpage_sect = '';
242         @manpage_names = ();
243         
244         $manpage_misc = '';
245         
246         $list_nestlevel = 0;            # Indent certain nested content.
247 });
248 sgml('</REFENTRY>', sub {
249         if(!$newline_last) {
250                 output "\n";
251         }
252         
253         $write_manpages = 0;
254         $raw_cdata = 1;
255         push_output('nul');
256 });
257
258 sgml('</REFMETA>', sub {
259         push_output('file', "$manpage_title.$manpage_sect");
260
261         output <<_END_BANNER;
262 .\\" This manpage has been automatically generated by docbook2man 
263 .\\" from a DocBook document.  This tool can be found at:
264 .\\" <http://shell.ipoline.com/~elmert/comp/docbook2X/> 
265 .\\" Please send any bug reports, improvements, comments, patches, 
266 .\\" etc. to Steve Cheng <steve\@ggi-project.org>.
267 _END_BANNER
268
269         my $manpage_date = `date "+%d %B %Y"`;
270                 
271         output '.TH "';
272         
273         # If the title is not mixed-case, convention says to
274         # uppercase the whole title.  (The canonical title is
275         # lowercase.)
276         if($manpage_title =~ /[A-Z]/) {
277                 output fold_string($manpage_title);
278         } else {
279                 output uc(fold_string($manpage_title));
280         }
281         
282         output  '" "', fold_string($manpage_sect), 
283                 '" "', fold_string(`date "+%d %B %Y"`), 
284                 '" "', $manpage_misc, 
285                 '" "', $manpage_manual, 
286                 "\"\n";
287
288         $newline_last = 1;
289
290         # References to this RefEntry.
291         my $id = $_[0]->parent->attribute('ID')->value;
292         if($id ne '') {
293                 # The 'package name' part of the section should
294                 # not be used when citing it.
295                 my ($sectnum) = ($manpage_sect =~ /([0-9]*)/);
296                 
297                 if($_[0]->parent->attribute('XREFLABEL')->value eq '') {
298                         $Refs->put("refentry:$id", "$manpage_title($sectnum)");
299                 } else {
300                         $Refs->put("refentry:$id",
301                                 $_[0]->parent->attribute('XREFLABEL')->value . 
302                                 "($sectnum)");
303                 }
304         }
305 });
306
307 sgml('<REFENTRYTITLE>', sub { 
308         if($_[0]->in('REFMETA')) { 
309                 save_cdata();
310         } else { 
311                 # Manpage citations are in bold.
312                 bold_on();
313         }
314 });
315 sgml('</REFENTRYTITLE>', sub { 
316         if($_[0]->in('REFMETA')) {
317                 $raw_cdata--;
318                 $manpage_title = pop_output();
319         }
320         else { font_off(); }
321 });
322
323 sgml('<MANVOLNUM>', sub { 
324         if($_[0]->in('REFMETA')) { 
325                 save_cdata();   
326         } else {
327                 # Manpage citations use ().
328                 output '(';
329         }
330 });
331 sgml('</MANVOLNUM>', sub { 
332         if($_[0]->in('REFMETA')) {
333                 $raw_cdata--;
334                 $manpage_sect = pop_output();
335         }
336         else { output ')' }
337 });
338
339 sgml('<REFMISCINFO>', \&save_cdata);
340 sgml('</REFMISCINFO>', sub { 
341         $raw_cdata--;
342         $manpage_misc = fold_string(pop_output());
343 });
344
345
346 # NAME section
347 man_sgml('<REFNAMEDIV>', "\n.SH NAME\n");
348
349 sgml('<REFNAME>', \&save_cdata);
350 sgml('</REFNAME>', sub { 
351         $raw_cdata--;
352         push(@manpage_names, pop_output());
353 });
354
355 sgml('<REFPURPOSE>', \&save_cdata);
356 sgml('</REFPURPOSE>', sub { 
357         $raw_cdata--;
358         my $manpage_purpose = fold_string(pop_output());
359         
360         for(my $i = 0; $i < $#manpage_names; $i++) {
361                 output fold_string($manpage_names[$i]), ', ';
362         }
363
364         output fold_string($manpage_names[$#manpage_names]);
365         output " \\- $manpage_purpose\n";
366
367         $newline_last = 1;
368
369         foreach(@manpage_names) {
370                 # Don't link to itself
371                 if($_ ne $manpage_title) {
372                         print LINKSFILE "$manpage_title.$manpage_sect   $_.$manpage_sect\n";
373                 }
374         }
375 });
376         
377 man_sgml('<REFCLASS>', "\n.sp\n");
378
379 #RefDescriptor
380
381
382
383
384
385 ########################################################################
386 #
387 # SYNOPSIS section and synopses
388 #
389 ########################################################################
390
391 man_sgml('<REFSYNOPSISDIV>', "\n.SH SYNOPSIS\n");
392 man_sgml('</REFSYNOPSISDIV>', "\n");
393
394 ## FIXME! Must be made into block elements!!
395 #sgml('<FUNCSYNOPSIS>', \&bold_on);
396 #sgml('</FUNCSYNOPSIS>', \&font_off);
397 #sgml('<CMDSYNOPSIS>', \&bold_on);
398 #sgml('</CMDSYNOPSIS>', \&font_off);
399
400 man_sgml('<FUNCSYNOPSIS>', sub {
401         man_output("\n.sp\n");
402         bold_on();
403 });
404 man_sgml('</FUNCSYNOPSIS>', sub {
405         font_off();
406         man_output("\n");
407 });
408
409 man_sgml('<CMDSYNOPSIS>', "\n\n");
410 man_sgml('</CMDSYNOPSIS>', "\n\n");
411
412 man_sgml('<FUNCPROTOTYPE>', "\n.sp\n");
413
414 # Arguments to functions.  This is C convention.
415 sub paramdef
416 {
417         if($_[0]->parent->ext->{'inparams'}) {
418                 output ', ';
419         } else {
420                 output ' (';
421                 $_[0]->parent->ext->{'inparams'} = 1;
422         }
423 }
424 man_sgml('<PARAMDEF>', \&paramdef);
425 man_sgml('</FUNCPROTOTYPE>', ");\n");
426 man_sgml('<VOID>', "(void");
427 man_sgml('<VARARGS>', "(...");
428
429
430
431 sub group_start
432 {
433         if(not $_[0]->parent->in('TERM')) {
434                 if($_[0]->attribute('CHOICE')->value =~ /opt/i) {
435                         output ' [';
436                 } elsif($_[0]->attribute('CHOICE')->value =~ /req/i) {
437                         output ' {';
438                 }
439         }
440         $_[0]->ext->{'count'} = 1;
441 }
442 sub group_end
443 {
444         if($_[0]->attribute('REP')->value =~ /^Repeat/i) {
445                 italic_on();
446                 output ' ...';
447                 font_off();
448         }
449         if(not $_[0]->parent->in('TERM')) {
450                 if($_[0]->attribute('CHOICE')->value =~ /opt/i) {
451                         output ' ]';
452                 } elsif($_[0]->attribute('CHOICE')->value =~ /req/i) {
453                         output ' }';
454                 }
455         }
456 }
457
458 sub arg_start
459 {
460         # my $choice = $_[0]->attribute('CHOICE')->value;
461
462         # The content model for CmdSynopsis doesn't include #PCDATA,
463         # so we won't see any of the whitespace in the source file,
464         # so we have to add it after each component.
465         output ' ';
466
467         if($_[0]->in('GROUP')) {
468                 output '| ' if $_[0]->parent->ext->{'count'} > 1;
469                 $_[0]->parent->ext->{'count'}++;
470         } elsif($_[0]->attribute('CHOICE')->value =~ /opt/i) {
471                 output '[ ';
472         }
473         bold_on();
474 }
475 sub arg_end
476 {
477         font_off();
478         if($_[0]->attribute('REP')->value =~ /^Repeat/i) {
479                 italic_on();
480                 output ' ...';
481                 font_off();
482         }
483         if($_[0]->attribute('CHOICE')->value =~ /opt/i and
484            not $_[0]->in('GROUP')) {
485                 output ' ]';
486         }
487 }
488
489 sgml('<ARG>', \&arg_start);
490 sgml('</ARG>', \&arg_end);
491 sgml('<GROUP>', \&group_start);
492 sgml('</GROUP>', \&group_end);
493
494 sgml('<OPTION>', \&bold_on);
495 sgml('</OPTION>', \&font_off);
496
497 man_sgml('<SBR>', "\n   ");
498
499
500 ########################################################################
501 #
502 # General sections
503 #
504 ########################################################################
505
506 # The name of the section is handled by TITLE.  This just sets
507 # up the roff markup.
508 man_sgml('<REFSECT1>', "\n.SH ");
509 man_sgml('<REFSECT2>', "\n.SS ");
510 man_sgml('<REFSECT3>', "\n.SS ");
511
512
513 ########################################################################
514 #
515 # Titles, metadata.
516 #
517 ########################################################################
518
519 sgml('<TITLE>', sub {
520         if($_[0]->in('REFERENCE') or $_[0]->in('BOOK')) {
521                 $write_manpages = 1;
522         }
523         save_cdata();
524 });
525 sgml('</TITLE>', sub { 
526         my $title = fold_string(pop_output());
527         $raw_cdata--;
528         
529         if($_[0]->in('REFERENCE') or $_[0]->in('BOOK')) {
530                 # We use TITLE of enclosing Reference or Book as manual name
531                 $manpage_manual = $title;
532                 $write_manpages = 0;
533         }
534         elsif(exists $_[0]->parent->ext->{'title'}) {
535                 # By far the easiest case.  Just fold the string as
536                 # above, and then set the parent element's variable.
537                 $_[0]->parent->ext->{'title'} = $title;
538         }
539         else {
540                 # If the parent element's handlers are lazy, 
541                 # output the folded string for them :)
542                 # We assume they want uppercase and a newline.
543                 output '"', uc($title), "\"\n";
544                 $newline_last = 1;
545         }
546 });
547
548 sgml('<ATTRIBUTION>', sub { push_output('string') });
549 sgml('</ATTRIBUTION>', sub { $_[0]->parent->ext->{'attribution'} = pop_output(); });
550
551
552 # IGNORE.
553 sgml('<DOCINFO>', sub { push_output('nul'); });
554 sgml('</DOCINFO>', sub { pop_output(); });
555 sgml('<REFSECT1INFO>', sub { push_output('nul'); });
556 sgml('</REFSECT1INFO>', sub { pop_output(); });
557 sgml('<REFSECT2INFO>', sub { push_output('nul'); });
558 sgml('</REFSECT2INFO>', sub { pop_output(); });
559 sgml('<REFSECT3INFO>', sub { push_output('nul'); });
560 sgml('</REFSECT3INFO>', sub { pop_output(); });
561
562 sgml('<INDEXTERM>', sub { push_output('nul'); });
563 sgml('</INDEXTERM>', sub { pop_output(); });
564
565
566 ########################################################################
567 #
568 # Set bold on enclosed content 
569 #
570 ########################################################################
571
572 sgml('<APPLICATION>', \&bold_on);       sgml('</APPLICATION>', \&font_off);
573
574 sgml('<CLASSNAME>', \&bold_on);         sgml('</CLASSNAME>', \&font_off);
575 sgml('<STRUCTNANE>', \&bold_on);        sgml('</STRUCTNAME>', \&font_off);
576 sgml('<STRUCTFIELD>', \&bold_on);       sgml('</STRUCTFIELD>', \&font_off);
577 sgml('<SYMBOL>', \&bold_on);            sgml('</SYMBOL>', \&font_off);
578 sgml('<TYPE>', \&bold_on);              sgml('</TYPE>', \&font_off);
579
580 sgml('<ENVAR>', \&bold_on);     sgml('</ENVAR>', \&font_off);
581
582 sgml('<FUNCTION>', \&bold_on);  sgml('</FUNCTION>', \&font_off);
583
584 sgml('<EMPHASIS>', \&bold_on);  sgml('</EMPHASIS>', \&font_off);
585
586 sgml('<ERRORNAME>', \&bold_on); sgml('</ERRORNAME>', \&font_off);
587 # ERRORTYPE
588
589 sgml('<COMMAND>', \&bold_on);   sgml('</COMMAND>', \&font_off);
590
591 sgml('<GUIBUTTON>', \&bold_on); sgml('</GUIBUTTON>', \&font_off);
592 sgml('<GUIICON>', \&bold_on);   sgml('</GUIICON>', \&font_off);
593 # GUILABEL
594 # GUIMENU
595 # GUIMENUITEM
596 # GUISUBMENU
597 # MENUCHOICE
598 # MOUSEBUTTON
599
600 sgml('<ACCEL>', \&bold_on);     sgml('</ACCEL>', \&font_off);
601 sgml('<KEYCAP>', \&bold_on);    sgml('</KEYCAP>', \&font_off);
602 sgml('<KEYSYM>', \&bold_on);    sgml('</KEYSYM>', \&font_off);
603 # KEYCODE
604 # KEYCOMBO
605 # SHORTCUT
606
607 sgml('<USERINPUT>', \&bold_on); sgml('</USERINPUT>', \&font_off);
608
609 sgml('<INTERFACEDEFINITION>', \&bold_on);
610 sgml('</INTERFACEDEFINITION>', \&font_off);
611
612 # May need to look at the CLASS
613 sgml('<SYSTEMITEM>', \&bold_on);
614 sgml('</SYSTEMITEM>', \&font_off);
615
616
617
618
619
620 ########################################################################
621 #
622 # Set italic on enclosed content 
623 #
624 ########################################################################
625
626 sgml('<FIRSTTERM>', \&italic_on);       sgml('</FIRSTTERM>', \&font_off);
627
628 sgml('<FILENAME>', \&italic_on);        sgml('</FILENAME>', \&font_off);
629 sgml('<PARAMETER>', \&italic_on);       sgml('</PARAMETER>', \&font_off);
630 sgml('<PROPERTY>', \&italic_on);        sgml('</PROPERTY>', \&font_off);
631
632 sgml('<REPLACEABLE>', sub {
633         italic_on();
634         if($_[0]->in('TOKEN')) {
635                 # When tokenizing, follow more 'intuitive' convention
636                 output "<";
637         }
638 });
639 sgml('</REPLACEABLE>', sub {
640         if($_[0]->in('TOKEN')) {
641                 output ">";
642         }
643         font_off();
644 });
645
646 sgml('<CITETITLE>', \&italic_on);       sgml('</CITETITLE>', \&font_off);
647 sgml('<FOREIGNPHRASE>', \&italic_on);   sgml('</FOREIGNPHRASE>', \&font_off);
648
649 sgml('<LINEANNOTATION>', \&italic_on);  sgml('</LINEANNOTATION>', \&font_off);
650
651
652
653
654
655
656 ########################################################################
657 #
658 # Other 'inline' elements 
659 #
660 ########################################################################
661
662 man_sgml('<EMAIL>', '<');
663 man_sgml('</EMAIL>', '>');
664 man_sgml('<OPTIONAL>', '[');
665 man_sgml('</OPTIONAL>', ']');
666
667 man_sgml('</TRADEMARK>', "\\u\\s-2TM\\s+2\\d");
668
669 man_sgml('<COMMENT>', "[Comment: ");
670 man_sgml('</COMMENT>', "]");
671
672 man_sgml('<QUOTE>', "``");
673 man_sgml('</QUOTE>', "''");
674
675 #man_sgml('<LITERAL>', '"');
676 #man_sgml('</LITERAL>', '"');
677
678 # No special presentation:
679
680 # AUTHOR
681 # AUTHORINITIALS
682
683 # ABBREV
684 # ACTION
685 # ACRONYM
686 # ALT
687 # CITATION
688 # PHRASE
689 # QUOTE
690 # WORDASWORD
691
692 # COMPUTEROUTPUT
693 # MARKUP
694 # PROMPT
695 # RETURNVALUE
696 # SGMLTAG
697 # TOKEN
698
699 # DATABASE
700 # HARDWARE
701 # INTERFACE
702 # MEDIALABEL
703
704 # There doesn't seem to be a good way to represent LITERAL in -man
705
706
707
708 ########################################################################
709 #
710 # Paragraph and paragraph-like elements 
711 #
712 ########################################################################
713
714 sub para_start {
715         output "\n" unless $newline_last++;
716
717         # In lists, etc., don't start paragraph with .PP since
718         # the indentation will be gone.
719
720         if($_[0]->parent->ext->{'nobreak'}==1) {
721                 # Usually this is the FIRST element of
722                 # a hanging tag, so we MUST not do a full
723                 # paragraph break.
724                 $_[0]->parent->ext->{'nobreak'} = 2;
725         } elsif($_[0]->parent->ext->{'nobreak'}==2) {
726                 # Usually these are the NEXT elements of
727                 # a hanging tag.  If we break using a blank
728                 # line, we're okay.
729                 output "\n";
730         } else {
731                 # Normal case. (For indented blocks too, at least
732                 # -man isn't so braindead in this area.)
733                 output ".PP\n";
734         }
735 }
736 # Actually applies to a few other block elements as well
737 sub para_end {
738         output "\n" unless $newline_last++; 
739 }
740
741 sgml('<PARA>', \&para_start);
742 sgml('</PARA>', \&para_end);
743 sgml('<SIMPARA>', \&para_start);
744 sgml('</SIMPARA>', \&para_end);
745
746 # Nothing special, except maybe FIXME set nobreak.
747 sgml('<INFORMALEXAMPLE>', \&para_start);
748 sgml('</INFORMALEXAMPLE>', \&para_end);
749
750
751
752
753
754 ########################################################################
755 #
756 # Blocks using SS sections
757 #
758 ########################################################################
759
760 # FIXME: We need to consider the effects of SS
761 # in a hanging tag :(
762
763 # Complete with the optional-title dilemma (again).
764 sgml('<ABSTRACT>', sub {
765         $_[0]->ext->{'title'} = 'ABSTRACT';
766         output "\n" unless $newline_last++;
767         push_output('string');
768 });
769 sgml('</ABSTRACT>', sub {
770         my $content = pop_output();
771         
772         # As ABSTRACT is never on the same level as RefSect1,
773         # this leaves us with only .SS in terms of -man macros.
774         output ".SS \"", uc($_[0]->ext->{'title'}), "\"\n";
775
776         output $content;
777         output "\n" unless $newline_last++;
778 });
779
780 # Ah, I needed a break.  Example always has a title.
781 man_sgml('<EXAMPLE>', "\n.SS ");
782 sgml('</EXAMPLE>', \&para_end);
783
784 # Same with sidebar.
785 man_sgml('<SIDEBAR>', "\n.SS ");
786 sgml('</SIDEBAR>', \&para_end);
787
788 # NO title.
789 man_sgml('<HIGHLIGHTS>', "\n.SS HIGHLIGHTS\n");
790 sgml('</HIGHLIGHTS>', \&para_end);
791
792
793
794
795 ########################################################################
796 #
797 # Indented 'Block' elements 
798 #
799 ########################################################################
800
801 sub indent_block_start
802 {
803         output "\n" unless $newline_last++;
804         output ".sp\n.RS\n";
805 }
806 sub indent_block_end
807 {
808         output "\n" unless $newline_last++;
809         output ".RE\n";
810 }
811
812 # This element is almost like an admonition (below),
813 # only the default title is blank :)
814
815 sgml('<BLOCKQUOTE>', sub { 
816         $_[0]->ext->{'title'} = ''; 
817         output "\n" unless $newline_last++;
818         push_output('string');
819 });
820 sgml('</BLOCKQUOTE>', sub {
821         my $content = pop_output();
822
823         indent_block_start();
824         
825         if($_[0]->ext->{'title'}) {
826                 output ".B \"", $_[0]->ext->{'title'}, ":\"\n";
827         }
828         
829         output $content;
830
831         if($_[0]->ext->{'attribution'}) {
832                 output "\n" unless $newline_last++;
833                 # One place where roff's space-sensitivity makes sense :)
834                 output "\n                -- ";
835                 output $_[0]->ext->{'attribution'} . "\n";
836         }
837         
838         indent_block_end();
839 });
840
841 # Set off admonitions from the rest of the text by indenting.
842 # FIXME: Need to check if this works inside paragraphs, not enclosing them.
843 sub admonition_end {
844         my $content = pop_output();
845
846         indent_block_start();
847         
848         # When the admonition is only one paragraph,
849         # it looks nicer if the title was inline.
850         my $num_para;
851         while ($content =~ /^\.PP/gm) { $num_para++ }
852         if($num_para==1) {
853                 $content =~ s/^\.PP\n//;
854         }
855         
856         output ".B \"" . $_[0]->ext->{'title'} . ":\"\n";
857         output $content;
858         
859         indent_block_end();
860 }
861
862 sgml('<NOTE>', sub {
863         # We can't see right now whether or not there is a TITLE
864         # element, so we have to save the output now and add it back
865         # at the end of this admonition.
866         $_[0]->ext->{'title'} = 'Note';
867
868         # Although admonition_end's indent_block_start will do this,
869         # we need to synchronize the output _now_
870         output "\n" unless $newline_last++;
871
872         push_output('string');
873 });
874 sgml('</NOTE>', \&admonition_end);
875
876 # Same as above.
877 sgml('<WARNING>', sub { 
878         $_[0]->ext->{'title'} = 'Warning'; 
879         output "\n" unless $newline_last++;
880         push_output('string');
881 });
882 sgml('</WARNING>', \&admonition_end);
883
884 sgml('<TIP>', sub {
885         $_[0]->ext->{'title'} = 'Tip';
886         output "\n" unless $newline_last++;
887         push_output('string');
888 });
889 sgml('</TIP>', \&admonition_end);
890 sgml('<CAUTION>', sub {
891         $_[0]->ext->{'title'} = 'Caution';
892         output "\n" unless $newline_last++;
893         push_output('string');
894 });
895 sgml('</CAUTION>', \&admonition_end);
896
897 sgml('<IMPORTANT>', sub {
898         $_[0]->ext->{'title'} = 'Important';
899         output "\n" unless $newline_last++;
900         push_output('string');
901 });
902 sgml('</IMPORTANT>', \&admonition_end);
903
904
905
906
907
908
909
910
911
912
913
914
915 ########################################################################
916 #
917 # Verbatim displays. 
918 #
919 ########################################################################
920
921 sub verbatim_start {
922         output "\n" unless $newline_last++;
923         
924         if($_[0]->parent->ext->{'nobreak'}==1) {
925                 # Usually this is the FIRST element of
926                 # a hanging tag, so we MUST not do a full
927                 # paragraph break.
928                 $_[0]->parent->ext->{'nobreak'} = 2;
929         } else {
930                 output "\n";
931         }
932         
933         output(".nf\n") unless $nocollapse_whitespace++;
934 }
935
936 sub verbatim_end {
937         output "\n" unless $newline_last++;
938         output(".fi\n") unless --$nocollapse_whitespace;
939 }
940
941 sgml('<PROGRAMLISTING>', \&verbatim_start); 
942 sgml('</PROGRAMLISTING>', \&verbatim_end);
943
944 sgml('<SCREEN>', \&verbatim_start); 
945 sgml('</SCREEN>', \&verbatim_end);
946
947 sgml('<LITERALLAYOUT>', \&verbatim_start); 
948 sgml('</LITERALLAYOUT>', \&verbatim_end);
949
950 #sgml('<SYNOPSIS>', sub {
951 #       if($_[0]->attribute('FORMAT')->value =~ /linespecific/i) {
952 #               &verbatim_start;
953 #       } else {
954 #               roffcmd("");
955 #       }
956 #});
957 #
958 #sgml('</SYNOPSIS>', sub {
959 #       if($_[0]->attribute('FORMAT')->value =~ /linespecific/i) {
960 #               &verbatim_end;
961 #       }
962 #       else {
963 #               roffcmd("");# not sure about this.
964 #       }
965 #});
966 sgml('<SYNOPSIS>', \&verbatim_start);
967 sgml('</SYNOPSIS>', \&verbatim_end);
968
969
970
971
972
973
974
975
976
977 ########################################################################
978 #
979 # Lists
980 #
981 ########################################################################
982
983 # Indent nested lists.
984 sub indent_list_start {
985         if($list_nestlevel++) {
986                 output "\n" unless $newline_last++;
987                 output ".RS\n";
988         }
989 }
990 sub indent_list_end {
991         if(--$list_nestlevel) {
992                 output "\n" unless $newline_last++;
993                 output ".RE\n";
994         }
995 }
996
997 sgml('<VARIABLELIST>', \&indent_list_start);
998 sgml('</VARIABLELIST>', \&indent_list_end);
999 sgml('<ITEMIZEDLIST>', \&indent_list_start);
1000 sgml('</ITEMIZEDLIST>', \&indent_list_end);
1001 sgml('<ORDEREDLIST>', sub { 
1002         indent_list_start();
1003         $_[0]->ext->{'count'} = 1;
1004 });
1005 sgml('</ORDEREDLIST>', \&indent_list_end);
1006 sgml('<GLOSSLIST>', \&indent_list_start);
1007 sgml('</GLOSSLIST>', \&indent_list_end);
1008                 
1009 # Output content on one line, bolded.
1010 sgml('<TERM>', sub { 
1011         output "\n" unless $newline_last++;
1012         output ".TP\n";
1013         bold_on();
1014         push_output('string');
1015 });
1016 sgml('</TERM>', sub { 
1017         my $term = pop_output();
1018         $term =~ tr/\n/ /;
1019         output $term;
1020         font_off();
1021         output "\n";
1022         $newline_last = 1;
1023 });
1024 sgml('<GLOSSTERM>', sub { 
1025         output "\n" unless $newline_last++;
1026         output ".TP\n";
1027         bold_on();
1028         push_output('string');
1029 });
1030 sgml('</GLOSSTERM>', sub { 
1031         my $term = pop_output();
1032         $term =~ tr/\n/ /;
1033         output $term;
1034         font_off();
1035         output "\n";
1036         $newline_last = 1;
1037 });
1038         
1039 sgml('<LISTITEM>', sub {
1040         # A bulleted list.
1041         if($_[0]->in('ITEMIZEDLIST')) {
1042                 output "\n" unless $newline_last++;
1043                 output ".TP 0.2i\n\\(bu\n";
1044         }
1045
1046         # Need numbers.
1047         # Assume Arabic numeration for now.
1048         elsif($_[0]->in('ORDEREDLIST')) {
1049                 output "\n" unless $newline_last++;
1050                 output ".TP 3\n", $_[0]->parent->ext->{'count'}++, ". \n";
1051         }
1052         
1053         $_[0]->ext->{'nobreak'} = 1;
1054 });
1055 sgml('<GLOSSDEF>', sub {
1056         $_[0]->ext->{'nobreak'} = 1;
1057 });
1058
1059 sgml('<SIMPLELIST>', sub {
1060         $_[0]->ext->{'first_member'} = 1;
1061 });
1062
1063 sgml('<MEMBER>', sub {
1064         my $parent = $_[0]->parent;
1065         
1066         if($parent->attribute('TYPE')->value =~ /Inline/i) {
1067                 if($parent->ext->{'first_member'}) { 
1068                         # If this is the first member don't put any commas
1069                         $parent->ext->{'first_member'} = 0;
1070                 } else {
1071                         output ", ";
1072                 }
1073         } elsif($parent->attribute('TYPE')->value =~ /Vert/i) {
1074                 output "\n" unless $newline_last++;
1075                 output "\n";
1076         }
1077 });
1078
1079
1080
1081
1082
1083 ########################################################################
1084 #
1085 # Stuff we don't know how to handle (yet) 
1086 #
1087 ########################################################################
1088
1089 # Address blocks:
1090
1091 # Credit stuff:
1092 # ACKNO
1093 # ADDRESS
1094 # AFFILIATION
1095 # ARTPAGENUMS
1096 # ATTRIBUTION
1097 # AUTHORBLURB
1098 # AUTHORGROUP
1099 # OTHERCREDIT
1100 # HONORIFIC
1101
1102 # Areas:
1103 # AREA
1104 # AREASET
1105 # AREASPEC
1106
1107
1108
1109
1110
1111 ########################################################################
1112 #
1113 # Linkage, cross references
1114 #
1115 ########################################################################
1116
1117 # Print the URL
1118 sgml('</ULINK>', sub {
1119         output ' <URL:', $_[0]->attribute('URL')->value, '>';
1120         $newline_last = 0;
1121 });
1122
1123 # If cross reference target is a RefEntry, 
1124 # output CiteRefEntry-style references.
1125 sgml('<XREF>', sub {
1126         my $id = $_[0]->attribute('LINKEND')->value;
1127         my $manref = $Refs->get("refentry:$id");
1128
1129         if($manref) {
1130                 my ($title, $sect) = ($manref =~ /(.*)(\(.*\))/);
1131                 bold_on();
1132                 output $title;
1133                 font_off();
1134                 output $sect;
1135         } else {
1136                 $blank_xrefs++ if $write_manpages;
1137                 output "[XRef to $id]";
1138         }
1139
1140         $newline_last = 0;
1141 });
1142
1143 # Anchor
1144
1145
1146
1147
1148 ########################################################################
1149 #
1150 # Other handlers 
1151 #
1152 ########################################################################
1153
1154 man_sgml('|[lt    ]|', '<');
1155 man_sgml('|[gt    ]|', '>');
1156 man_sgml('|[amp   ]|', '&');
1157
1158 #
1159 # Default handlers (uncomment these if needed).  Right now, these are set
1160 # up to gag on any unrecognised elements, sdata, processing-instructions,
1161 # or entities.
1162 #
1163 # sgml('start_element',sub { die "Unknown element: " . $_[0]->name; });
1164 # sgml('end_element','');
1165
1166 # This is for weeding out and escaping certain characters.
1167 # This looks like it's inefficient since it's done on every line, but
1168 # in reality, SGMLSpm and sgmlspl parsing ESIS takes _much_ longer.
1169
1170 sgml('cdata', sub
1171
1172         if(!$write_manpages) { return; }
1173         elsif($raw_cdata) { output $_[0]; return; }
1174         
1175         # Escape backslashes
1176         $_[0] =~ s/\\/\\\\/g;
1177
1178         # In non-'pre'-type elements:
1179         if(!$nocollapse_whitespace) {
1180                 # Change tabs to spaces
1181                 $_[0] =~ tr/\t/ /;
1182
1183                 # Do not allow indents at beginning of line
1184                 # groff chokes on that.
1185                 if($newline_last) { 
1186                         $_[0] =~ s/^ +//;
1187
1188                         # If the line is all blank, don't do anything.
1189                         if($_[0] eq '') { return; }
1190                         
1191                         $_[0] =~ s/^\./\\\&\./;
1192
1193                         # Argh... roff doesn't like ' either...
1194                         $_[0] =~ s/^\'/\\\&\'/;
1195                 }
1196         }
1197
1198         $newline_last = 0;
1199
1200         output $_[0];
1201 });
1202
1203
1204 # When in whitespace-collapsing mode, we disallow consecutive newlines.
1205
1206 sgml('re', sub
1207 {
1208         if($nocollapse_whitespace || !$newline_last) {
1209                 output "\n";
1210         }
1211
1212         $newline_last = 1;
1213 });
1214
1215 sgml('sdata',sub
1216 {
1217         if($_[0] =~ /\[minus \]/) { output "-"; }
1218         elsif($_[0] =~ /\[copy  \]/) { output "(C)"; }
1219         else { die "Unknown SDATA: " . $_[0]; }
1220 });
1221 sgml('pi',sub { die "Unknown processing instruction: " . $_[0]; });
1222 sgml('entity',sub { die "Unknown external entity: " . $_[0]->name; });
1223 sgml('start_subdoc',sub { die "Unknown subdoc entity: " . $_[0]->name; });
1224 sgml('end_subdoc','');
1225 sgml('conforming','');
1226
1227 1;
1228