1=head1 NAME
2
3docbook2man-spec - convert DocBook RefEntries to Unix manpages
4
5=head1 SYNOPSIS
6
7The SGMLSpm package from CPAN.  This contains the sgmlspl script which
8is used to grok this file.  Use it like this:
9
10nsgmls some-docbook-document.sgml | sgmlspl docbook2man-spec.pl
11
12=head1 DESCRIPTION
13
14This is a sgmlspl spec file that produces Unix-style
15manpages from RefEntry markup.
16
17See the accompanying RefEntry man page for 'plain new' documentation. :)
18
19=head1 LIMITATIONS
20
21Trying docbook2man on non-DocBook or non-conformant SGML results in
22undefined behavior. :-)
23
24This program is a slow, dodgy Perl script.
25
26This program does not come close to supporting all the possible markup
27in DocBook, and will produce wrong output in some cases with supported
28markup.
29
30=head1 TODO
31
32Add new element handling and fix existing handling.  Be robust.
33Produce cleanest, readable man output as possible (unlike some
34other converters).  Follow Linux man(7) convention.
35If this results in added logic in this script,
36that's okay.  The code should still be reasonably organized.
37
38Make it faster.  If Perl sucks port it to another language.
39
40=head1 COPYRIGHT
41
42Copyright (C) 1998-1999 Steve Cheng <steve@ggi-project.org>
43
44This program is free software; you can redistribute it and/or modify it
45under the terms of the GNU General Public License as published by the Free
46Software Foundation; either version 2, or (at your option) any later
47version.
48
49You should have received a copy of the GNU General Public License along with
50this program; see the file COPYING.  If not, please write to the Free
51Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
52
53=cut
54
55# $Id: docbook2man-spec.pl,v 1.1 2000/07/21 20:22:30 rosalia Exp $
56
57use SGMLS;			# Use the SGMLS package.
58use SGMLS::Output;		# Use stack-based output.
59use 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
71sgml('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});
80sgml('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
99sub 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
129sub 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
148sub 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
165sub save_cdata()
166{
167	$raw_cdata++;
168	push_output('string');
169}
170
171sub 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
184sub 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
196sub 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
223sgml('<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});
248sgml('</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
258sgml('</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
307sgml('<REFENTRYTITLE>', sub {
308	if($_[0]->in('REFMETA')) {
309		save_cdata();
310	} else {
311		# Manpage citations are in bold.
312		bold_on();
313	}
314});
315sgml('</REFENTRYTITLE>', sub {
316	if($_[0]->in('REFMETA')) {
317		$raw_cdata--;
318		$manpage_title = pop_output();
319	}
320	else { font_off(); }
321});
322
323sgml('<MANVOLNUM>', sub {
324	if($_[0]->in('REFMETA')) {
325		save_cdata();
326	} else {
327		# Manpage citations use ().
328		output '(';
329	}
330});
331sgml('</MANVOLNUM>', sub {
332	if($_[0]->in('REFMETA')) {
333		$raw_cdata--;
334		$manpage_sect = pop_output();
335	}
336	else { output ')' }
337});
338
339sgml('<REFMISCINFO>', \&save_cdata);
340sgml('</REFMISCINFO>', sub {
341	$raw_cdata--;
342	$manpage_misc = fold_string(pop_output());
343});
344
345
346# NAME section
347man_sgml('<REFNAMEDIV>', "\n.SH NAME\n");
348
349sgml('<REFNAME>', \&save_cdata);
350sgml('</REFNAME>', sub {
351	$raw_cdata--;
352	push(@manpage_names, pop_output());
353});
354
355sgml('<REFPURPOSE>', \&save_cdata);
356sgml('</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
377man_sgml('<REFCLASS>', "\n.sp\n");
378
379#RefDescriptor
380
381
382
383
384
385########################################################################
386#
387# SYNOPSIS section and synopses
388#
389########################################################################
390
391man_sgml('<REFSYNOPSISDIV>', "\n.SH SYNOPSIS\n");
392man_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
400man_sgml('<FUNCSYNOPSIS>', sub {
401	man_output("\n.sp\n");
402	bold_on();
403});
404man_sgml('</FUNCSYNOPSIS>', sub {
405	font_off();
406	man_output("\n");
407});
408
409man_sgml('<CMDSYNOPSIS>', "\n\n");
410man_sgml('</CMDSYNOPSIS>', "\n\n");
411
412man_sgml('<FUNCPROTOTYPE>', "\n.sp\n");
413
414# Arguments to functions.  This is C convention.
415man_sgml('<PARAMDEF>', '(');
416man_sgml('</PARAMDEF>', ");\n");
417man_sgml('<VOID>', "(void);\n");
418
419
420
421sub arg_start
422{
423	# my $choice = $_[0]->attribute('CHOICE')->value;
424
425	# The content model for CmdSynopsis doesn't include #PCDATA,
426	# so we won't see any of the whitespace in the source file,
427	# so we have to add it after each component.
428	output ' ';
429
430	if($_[0]->attribute('CHOICE')->value =~ /opt/i) {
431		output '[';
432	}
433	bold_on();
434}
435sub arg_end
436{
437	font_off();
438	if($_[0]->attribute('REP')->value =~ /^Repeat/i) {
439		italic_on();
440		output ' ...';
441		font_off();
442	}
443	if($_[0]->attribute('CHOICE')->value =~ /opt/i) {
444		output ']';
445	}
446}
447
448sgml('<ARG>', \&arg_start);
449sgml('</ARG>', \&arg_end);
450sgml('<GROUP>', \&arg_start);
451sgml('</GROUP>', \&arg_end);
452
453sgml('<OPTION>', \&bold_on);
454sgml('</OPTION>', \&font_off);
455
456# FIXME: This is one _blank_ line.
457man_sgml('<SBR>', "\n\n");
458
459
460########################################################################
461#
462# General sections
463#
464########################################################################
465
466# The name of the section is handled by TITLE.  This just sets
467# up the roff markup.
468man_sgml('<REFSECT1>', "\n.SH ");
469man_sgml('<REFSECT2>', "\n.SS ");
470man_sgml('<REFSECT3>', "\n.SS ");
471
472
473########################################################################
474#
475# Titles, metadata.
476#
477########################################################################
478
479sgml('<TITLE>', sub {
480	if($_[0]->in('REFERENCE') or $_[0]->in('BOOK')) {
481		$write_manpages = 1;
482	}
483	save_cdata();
484});
485sgml('</TITLE>', sub {
486	my $title = fold_string(pop_output());
487	$raw_cdata--;
488
489	if($_[0]->in('REFERENCE') or $_[0]->in('BOOK')) {
490		# We use TITLE of enclosing Reference or Book as manual name
491		$manpage_manual = $title;
492		$write_manpages = 0;
493	}
494	elsif(exists $_[0]->parent->ext->{'title'}) {
495		# By far the easiest case.  Just fold the string as
496		# above, and then set the parent element's variable.
497		$_[0]->parent->ext->{'title'} = $title;
498	}
499	else {
500		# If the parent element's handlers are lazy,
501		# output the folded string for them :)
502		# We assume they want uppercase and a newline.
503		output '"', uc($title), "\"\n";
504		$newline_last = 1;
505	}
506});
507
508sgml('<ATTRIBUTION>', sub { push_output('string') });
509sgml('</ATTRIBUTION>', sub { $_[0]->parent->ext->{'attribution'} = pop_output(); });
510
511
512# IGNORE.
513sgml('<DOCINFO>', sub { push_output('nul'); });
514sgml('</DOCINFO>', sub { pop_output(); });
515sgml('<REFSECT1INFO>', sub { push_output('nul'); });
516sgml('</REFSECT1INFO>', sub { pop_output(); });
517sgml('<REFSECT2INFO>', sub { push_output('nul'); });
518sgml('</REFSECT2INFO>', sub { pop_output(); });
519sgml('<REFSECT3INFO>', sub { push_output('nul'); });
520sgml('</REFSECT3INFO>', sub { pop_output(); });
521
522sgml('<INDEXTERM>', sub { push_output('nul'); });
523sgml('</INDEXTERM>', sub { pop_output(); });
524
525
526########################################################################
527#
528# Set bold on enclosed content
529#
530########################################################################
531
532sgml('<APPLICATION>', \&bold_on);	sgml('</APPLICATION>', \&font_off);
533
534sgml('<CLASSNAME>', \&bold_on);		sgml('</CLASSNAME>', \&font_off);
535sgml('<STRUCTNANE>', \&bold_on);	sgml('</STRUCTNAME>', \&font_off);
536sgml('<STRUCTFIELD>', \&bold_on);	sgml('</STRUCTFIELD>', \&font_off);
537sgml('<SYMBOL>', \&bold_on);		sgml('</SYMBOL>', \&font_off);
538sgml('<TYPE>', \&bold_on);		sgml('</TYPE>', \&font_off);
539
540sgml('<ENVAR>', \&bold_on);	sgml('</ENVAR>', \&font_off);
541
542sgml('<FUNCTION>', \&bold_on);	sgml('</FUNCTION>', \&font_off);
543
544sgml('<EMPHASIS>', \&bold_on);	sgml('</EMPHASIS>', \&font_off);
545
546sgml('<ERRORNAME>', \&bold_on);	sgml('</ERRORNAME>', \&font_off);
547# ERRORTYPE
548
549sgml('<COMMAND>', \&bold_on);	sgml('</COMMAND>', \&font_off);
550
551sgml('<GUIBUTTON>', \&bold_on);	sgml('</GUIBUTTON>', \&font_off);
552sgml('<GUIICON>', \&bold_on);	sgml('</GUIICON>', \&font_off);
553# GUILABEL
554# GUIMENU
555# GUIMENUITEM
556# GUISUBMENU
557# MENUCHOICE
558# MOUSEBUTTON
559
560sgml('<ACCEL>', \&bold_on);	sgml('</ACCEL>', \&font_off);
561sgml('<KEYCAP>', \&bold_on);	sgml('</KEYCAP>', \&font_off);
562sgml('<KEYSYM>', \&bold_on);	sgml('</KEYSYM>', \&font_off);
563# KEYCODE
564# KEYCOMBO
565# SHORTCUT
566
567sgml('<USERINPUT>', \&bold_on);	sgml('</USERINPUT>', \&font_off);
568
569sgml('<INTERFACEDEFINITION>', \&bold_on);
570sgml('</INTERFACEDEFINITION>', \&font_off);
571
572# May need to look at the CLASS
573sgml('<SYSTEMITEM>', \&bold_on);
574sgml('</SYSTEMITEM>', \&font_off);
575
576
577
578
579
580########################################################################
581#
582# Set italic on enclosed content
583#
584########################################################################
585
586sgml('<FIRSTTERM>', \&italic_on);	sgml('</FIRSTTERM>', \&font_off);
587
588sgml('<FILENAME>', \&italic_on);	sgml('</FILENAME>', \&font_off);
589sgml('<PARAMETER>', \&italic_on);	sgml('</PARAMETER>', \&font_off);
590sgml('<PROPERTY>', \&italic_on);	sgml('</PROPERTY>', \&font_off);
591
592sgml('<REPLACEABLE>', sub {
593	italic_on();
594	if($_[0]->in('TOKEN')) {
595		# When tokenizing, follow more 'intuitive' convention
596		output "<";
597	}
598});
599sgml('</REPLACEABLE>', sub {
600	if($_[0]->in('TOKEN')) {
601		output ">";
602	}
603	font_off();
604});
605
606sgml('<CITETITLE>', \&italic_on);	sgml('</CITETITLE>', \&font_off);
607sgml('<FOREIGNPHRASE>', \&italic_on);	sgml('</FOREIGNPHRASE>', \&font_off);
608
609sgml('<LINEANNOTATION>', \&italic_on);	sgml('</LINEANNOTATION>', \&font_off);
610
611
612
613
614
615
616########################################################################
617#
618# Other 'inline' elements
619#
620########################################################################
621
622man_sgml('<EMAIL>', '<');
623man_sgml('</EMAIL>', '>');
624man_sgml('<OPTIONAL>', '[');
625man_sgml('</OPTIONAL>', ']');
626
627man_sgml('</TRADEMARK>', "\\u\\s-2TM\\s+2\\d");
628
629man_sgml('<COMMENT>', "[Comment: ");
630man_sgml('</COMMENT>', "]");
631
632man_sgml('<QUOTE>', "``");
633man_sgml('</QUOTE>', "''");
634
635#man_sgml('<LITERAL>', '"');
636#man_sgml('</LITERAL>', '"');
637
638# No special presentation:
639
640# AUTHOR
641# AUTHORINITIALS
642
643# ABBREV
644# ACTION
645# ACRONYM
646# ALT
647# CITATION
648# PHRASE
649# QUOTE
650# WORDASWORD
651
652# COMPUTEROUTPUT
653# MARKUP
654# PROMPT
655# RETURNVALUE
656# SGMLTAG
657# TOKEN
658
659# DATABASE
660# HARDWARE
661# INTERFACE
662# MEDIALABEL
663
664# There doesn't seem to be a good way to represent LITERAL in -man
665
666
667
668########################################################################
669#
670# Paragraph and paragraph-like elements
671#
672########################################################################
673
674sub para_start {
675	output "\n" unless $newline_last++;
676
677	# In lists, etc., don't start paragraph with .PP since
678	# the indentation will be gone.
679
680	if($_[0]->parent->ext->{'nobreak'}==1) {
681		# Usually this is the FIRST element of
682		# a hanging tag, so we MUST not do a full
683		# paragraph break.
684		$_[0]->parent->ext->{'nobreak'} = 2;
685	} elsif($_[0]->parent->ext->{'nobreak'}==2) {
686		# Usually these are the NEXT elements of
687		# a hanging tag.  If we break using a blank
688		# line, we're okay.
689		output "\n";
690	} else {
691		# Normal case. (For indented blocks too, at least
692		# -man isn't so braindead in this area.)
693		output ".PP\n";
694	}
695}
696# Actually applies to a few other block elements as well
697sub para_end {
698	output "\n" unless $newline_last++;
699}
700
701sgml('<PARA>', \&para_start);
702sgml('</PARA>', \&para_end);
703sgml('<SIMPARA>', \&para_start);
704sgml('</SIMPARA>', \&para_end);
705
706# Nothing special, except maybe FIXME set nobreak.
707sgml('<INFORMALEXAMPLE>', \&para_start);
708sgml('</INFORMALEXAMPLE>', \&para_end);
709
710
711
712
713
714########################################################################
715#
716# Blocks using SS sections
717#
718########################################################################
719
720# FIXME: We need to consider the effects of SS
721# in a hanging tag :(
722
723# Complete with the optional-title dilemma (again).
724sgml('<ABSTRACT>', sub {
725	$_[0]->ext->{'title'} = 'ABSTRACT';
726	output "\n" unless $newline_last++;
727	push_output('string');
728});
729sgml('</ABSTRACT>', sub {
730	my $content = pop_output();
731
732	# As ABSTRACT is never on the same level as RefSect1,
733	# this leaves us with only .SS in terms of -man macros.
734	output ".SS \"", uc($_[0]->ext->{'title'}), "\"\n";
735
736	output $content;
737	output "\n" unless $newline_last++;
738});
739
740# Ah, I needed a break.  Example always has a title.
741man_sgml('<EXAMPLE>', "\n.SS ");
742sgml('</EXAMPLE>', \&para_end);
743
744# Same with sidebar.
745man_sgml('<SIDEBAR>', "\n.SS ");
746sgml('</SIDEBAR>', \&para_end);
747
748# NO title.
749man_sgml('<HIGHLIGHTS>', "\n.SS HIGHLIGHTS\n");
750sgml('</HIGHLIGHTS>', \&para_end);
751
752
753
754
755########################################################################
756#
757# Indented 'Block' elements
758#
759########################################################################
760
761sub indent_block_start
762{
763	output "\n" unless $newline_last++;
764	output ".sp\n.RS\n";
765}
766sub indent_block_end
767{
768	output "\n" unless $newline_last++;
769	output ".RE\n";
770}
771
772# This element is almost like an admonition (below),
773# only the default title is blank :)
774
775sgml('<BLOCKQUOTE>', sub {
776	$_[0]->ext->{'title'} = '';
777	output "\n" unless $newline_last++;
778	push_output('string');
779});
780sgml('</BLOCKQUOTE>', sub {
781	my $content = pop_output();
782
783	indent_block_start();
784
785	if($_[0]->ext->{'title'}) {
786		output ".B \"", $_[0]->ext->{'title'}, ":\"\n";
787	}
788
789	output $content;
790
791	if($_[0]->ext->{'attribution'}) {
792		output "\n" unless $newline_last++;
793		# One place where roff's space-sensitivity makes sense :)
794		output "\n                -- ";
795		output $_[0]->ext->{'attribution'} . "\n";
796	}
797
798	indent_block_end();
799});
800
801# Set off admonitions from the rest of the text by indenting.
802# FIXME: Need to check if this works inside paragraphs, not enclosing them.
803sub admonition_end {
804	my $content = pop_output();
805
806	indent_block_start();
807
808	# When the admonition is only one paragraph,
809	# it looks nicer if the title was inline.
810	my $num_para;
811	while ($content =~ /^\.PP/gm) { $num_para++ }
812	if($num_para==1) {
813		$content =~ s/^\.PP\n//;
814	}
815
816	output ".B \"" . $_[0]->ext->{'title'} . ":\"\n";
817	output $content;
818
819	indent_block_end();
820}
821
822sgml('<NOTE>', sub {
823	# We can't see right now whether or not there is a TITLE
824	# element, so we have to save the output now and add it back
825	# at the end of this admonition.
826	$_[0]->ext->{'title'} = 'Note';
827
828	# Although admonition_end's indent_block_start will do this,
829	# we need to synchronize the output _now_
830	output "\n" unless $newline_last++;
831
832	push_output('string');
833});
834sgml('</NOTE>', \&admonition_end);
835
836# Same as above.
837sgml('<WARNING>', sub {
838	$_[0]->ext->{'title'} = 'Warning';
839	output "\n" unless $newline_last++;
840	push_output('string');
841});
842sgml('</WARNING>', \&admonition_end);
843
844sgml('<TIP>', sub {
845	$_[0]->ext->{'title'} = 'Tip';
846	output "\n" unless $newline_last++;
847	push_output('string');
848});
849sgml('</TIP>', \&admonition_end);
850sgml('<CAUTION>', sub {
851	$_[0]->ext->{'title'} = 'Caution';
852	output "\n" unless $newline_last++;
853	push_output('string');
854});
855sgml('</CAUTION>', \&admonition_end);
856
857sgml('<IMPORTANT>', sub {
858	$_[0]->ext->{'title'} = 'Important';
859	output "\n" unless $newline_last++;
860	push_output('string');
861});
862sgml('</IMPORTANT>', \&admonition_end);
863
864
865
866
867
868
869
870
871
872
873
874
875########################################################################
876#
877# Verbatim displays.
878#
879########################################################################
880
881sub verbatim_start {
882	output "\n" unless $newline_last++;
883
884	if($_[0]->parent->ext->{'nobreak'}==1) {
885		# Usually this is the FIRST element of
886		# a hanging tag, so we MUST not do a full
887		# paragraph break.
888		$_[0]->parent->ext->{'nobreak'} = 2;
889	} else {
890		output "\n";
891	}
892
893	output(".nf\n") unless $nocollapse_whitespace++;
894}
895
896sub verbatim_end {
897	output "\n" unless $newline_last++;
898	output(".fi\n") unless --$nocollapse_whitespace;
899}
900
901sgml('<PROGRAMLISTING>', \&verbatim_start);
902sgml('</PROGRAMLISTING>', \&verbatim_end);
903
904sgml('<SCREEN>', \&verbatim_start);
905sgml('</SCREEN>', \&verbatim_end);
906
907sgml('<LITERALLAYOUT>', \&verbatim_start);
908sgml('</LITERALLAYOUT>', \&verbatim_end);
909
910#sgml('<SYNOPSIS>', sub {
911#	if($_[0]->attribute('FORMAT')->value =~ /linespecific/i) {
912#		&verbatim_start;
913#	} else {
914#		roffcmd("");
915#	}
916#});
917#
918#sgml('</SYNOPSIS>', sub {
919#	if($_[0]->attribute('FORMAT')->value =~ /linespecific/i) {
920#		&verbatim_end;
921#	}
922#	else {
923#		roffcmd("");# not sure about this.
924#	}
925#});
926sgml('<SYNOPSIS>', \&verbatim_start);
927sgml('</SYNOPSIS>', \&verbatim_end);
928
929
930
931
932
933
934
935
936
937########################################################################
938#
939# Lists
940#
941########################################################################
942
943# Indent nested lists.
944sub indent_list_start {
945	if($list_nestlevel++) {
946		output "\n" unless $newline_last++;
947		output ".RS\n";
948	}
949}
950sub indent_list_end {
951	if(--$list_nestlevel) {
952		output "\n" unless $newline_last++;
953		output ".RE\n";
954	}
955}
956
957sgml('<VARIABLELIST>', \&indent_list_start);
958sgml('</VARIABLELIST>', \&indent_list_end);
959sgml('<ITEMIZEDLIST>', \&indent_list_start);
960sgml('</ITEMIZEDLIST>', \&indent_list_end);
961sgml('<ORDEREDLIST>', sub {
962	indent_list_start();
963	$_[0]->ext->{'count'} = 1;
964});
965sgml('</ORDEREDLIST>', \&indent_list_end);
966
967# Output content on one line, bolded.
968sgml('<TERM>', sub {
969	output "\n" unless $newline_last++;
970	output ".TP\n";
971	bold_on();
972	push_output('string');
973});
974sgml('</TERM>', sub {
975	my $term = pop_output();
976	$term =~ tr/\n/ /;
977	output $term;
978	font_off();
979	output "\n";
980	$newline_last = 1;
981});
982
983sgml('<LISTITEM>', sub {
984	# A bulleted list.
985	if($_[0]->in('ITEMIZEDLIST')) {
986		output "\n" unless $newline_last++;
987		output ".TP 0.2i\n\\(bu\n";
988	}
989
990	# Need numbers.
991	# Assume Arabic numeration for now.
992	elsif($_[0]->in('ORDEREDLIST')) {
993		output "\n" unless $newline_last++;
994		output ".TP ", $_[0]->parent->ext->{'count'}++, ". \n";
995	}
996
997	$_[0]->ext->{'nobreak'} = 1;
998});
999
1000sgml('<SIMPLELIST>', sub {
1001	$_[0]->ext->{'first_member'} = 1;
1002});
1003
1004sgml('<MEMBER>', sub {
1005	my $parent = $_[0]->parent;
1006
1007	if($parent->attribute('TYPE')->value =~ /Inline/i) {
1008		if($parent->ext->{'first_member'}) {
1009			# If this is the first member don't put any commas
1010			$parent->ext->{'first_member'} = 0;
1011		} else {
1012			output ", ";
1013		}
1014	} elsif($parent->attribute('TYPE')->value =~ /Vert/i) {
1015		output "\n" unless $newline_last++;
1016		output "\n";
1017	}
1018});
1019
1020
1021
1022
1023
1024########################################################################
1025#
1026# Stuff we don't know how to handle (yet)
1027#
1028########################################################################
1029
1030# Address blocks:
1031
1032# Credit stuff:
1033# ACKNO
1034# ADDRESS
1035# AFFILIATION
1036# ARTPAGENUMS
1037# ATTRIBUTION
1038# AUTHORBLURB
1039# AUTHORGROUP
1040# OTHERCREDIT
1041# HONORIFIC
1042
1043# Areas:
1044# AREA
1045# AREASET
1046# AREASPEC
1047
1048
1049
1050
1051
1052########################################################################
1053#
1054# Linkage, cross references
1055#
1056########################################################################
1057
1058# Print the URL
1059sgml('</ULINK>', sub {
1060#	output ' <URL:', $_[0]->attribute('URL')->value, '>';
1061	$newline_last = 0;
1062});
1063
1064# If cross reference target is a RefEntry,
1065# output CiteRefEntry-style references.
1066sgml('<XREF>', sub {
1067	my $id = $_[0]->attribute('LINKEND')->value;
1068	my $manref = $Refs->get("refentry:$id");
1069
1070	if($manref) {
1071		my ($title, $sect) = ($manref =~ /(.*)(\(.*\))/);
1072		bold_on();
1073		output $title;
1074		font_off();
1075		output $sect;
1076	} else {
1077		$blank_xrefs++ if $write_manpages;
1078		output "[XRef to $id]";
1079	}
1080
1081	$newline_last = 0;
1082});
1083
1084# Anchor
1085
1086
1087
1088
1089########################################################################
1090#
1091# Other handlers
1092#
1093########################################################################
1094
1095man_sgml('|[lt    ]|', '<');
1096man_sgml('|[gt    ]|', '>');
1097man_sgml('|[amp   ]|', '&');
1098
1099#
1100# Default handlers (uncomment these if needed).  Right now, these are set
1101# up to gag on any unrecognised elements, sdata, processing-instructions,
1102# or entities.
1103#
1104# sgml('start_element',sub { die "Unknown element: " . $_[0]->name; });
1105# sgml('end_element','');
1106
1107# This is for weeding out and escaping certain characters.
1108# This looks like it's inefficient since it's done on every line, but
1109# in reality, SGMLSpm and sgmlspl parsing ESIS takes _much_ longer.
1110
1111sgml('cdata', sub
1112{
1113	if(!$write_manpages) { return; }
1114	elsif($raw_cdata) { output $_[0]; return; }
1115
1116	# Escape backslashes
1117	$_[0] =~ s/\\/\\\\/g;
1118
1119	# In non-'pre'-type elements:
1120	if(!$nocollapse_whitespace) {
1121		# Change tabs to spaces
1122		$_[0] =~ tr/\t/ /;
1123
1124		# Do not allow indents at beginning of line
1125		# groff chokes on that.
1126		if($newline_last) {
1127			$_[0] =~ s/^ +//;
1128
1129			# If the line is all blank, don't do anything.
1130			if($_[0] eq '') { return; }
1131
1132			$_[0] =~ s/^\./\\\&\./;
1133
1134			# Argh... roff doesn't like ' either...
1135			$_[0] =~ s/^\'/\\\&\'/;
1136		}
1137	}
1138
1139	$newline_last = 0;
1140
1141	output $_[0];
1142});
1143
1144
1145# When in whitespace-collapsing mode, we disallow consecutive newlines.
1146
1147sgml('re', sub
1148{
1149	if($nocollapse_whitespace || !$newline_last) {
1150		output "\n";
1151	}
1152
1153	$newline_last = 1;
1154});
1155
1156sgml('sdata',sub { die "Unknown SDATA: " . $_[0]; });
1157sgml('pi',sub { die "Unknown processing instruction: " . $_[0]; });
1158sgml('entity',sub { die "Unknown external entity: " . $_[0]->name; });
1159sgml('start_subdoc',sub { die "Unknown subdoc entity: " . $_[0]->name; });
1160sgml('end_subdoc','');
1161sgml('conforming','');
1162
11631;
1164
1165