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