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