Home | History | Annotate | Download | only in doc
      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 (at] ggi-project.org>
     43 
     44 This program is free software; you can redistribute it and/or modify it
     45 under the terms of the GNU General Public License as published by the Free
     46 Software Foundation; either version 2, or (at your option) any later
     47 version.
     48 
     49 You should have received a copy of the GNU General Public License along with
     50 this program; see the file COPYING.  If not, please write to the Free
     51 Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
     52 
     53 =cut
     54 
     55 # $Id: docbook2man-spec.pl,v 1.1 2000/07/21 20:22:30 rosalia Exp $
     56 
     57 use SGMLS;			# Use the SGMLS package.
     58 use SGMLS::Output;		# Use stack-based output.
     59 use SGMLS::Refs;
     60 
     61 ########################################################################
     62 # SGMLSPL script produced automatically by the script sgmlspl.pl
     63 #
     64 # Document Type: any, but processes only RefEntries
     65 # Edited by: me :)
     66 ########################################################################
     67 
     68 $write_manpages = 0;
     69 $blank_xrefs = 0;
     70 
     71 sgml('start', sub { 
     72 	push_output('nul');
     73 	$raw_cdata = 1;			# Makes it a bit faster.
     74 	
     75 	# Links file
     76 	open(LINKSFILE, ">manpage.links");
     77 
     78 	$Refs = new SGMLS::Refs("manpage.refs");
     79 });
     80 sgml('end', sub {
     81 	close(LINKSFILE);
     82 	if($blank_xrefs) {
     83 		print STDERR "Warning: output contains unresolved XRefs\n";
     84 	}
     85 });
     86 
     87 
     88 
     89 
     90 ########################################################################
     91 #
     92 # Output helpers 
     93 #
     94 ########################################################################
     95 
     96 # Our own version of sgml() and output() to allow simple string output
     97 # to play well with roff's stupid whitespace rules. 
     98 
     99 sub man_sgml
    100 {
    101 	if(ref($_[1]) eq 'CODE') {
    102 		return &sgml;
    103 	}
    104 	
    105 	my $s = $_[1];
    106 
    107 	$s =~ s/\\/\\\\/g;
    108 	$s =~ s/'/\\'/g;
    109 
    110 	# \n at the beginning means start at beginning of line
    111 	if($s =~ s/^\n//) {
    112 		$sub = 'sub { output "\n" unless $newline_last++; ';
    113 		if($s eq '') { 
    114 			sgml($_[0], eval('sub { output "\n" unless $newline_last++; }'));
    115 		} elsif($s =~ /\n$/) {
    116 			sgml($_[0], eval("sub { output \"\\n\" unless \$newline_last++; output '$s'; }"));
    117 		} else {
    118 			sgml($_[0], eval("sub { output \"\\n\" unless \$newline_last; output '$s'; \$newline_last = 0; }"));
    119 		}
    120 	} else {
    121 		if($s =~ /\n$/) {
    122 			sgml($_[0], eval("sub { output '$s'; \$newline_last = 1; }"));
    123 		} else {
    124 			sgml($_[0], eval("sub { output '$s'; \$newline_last = 0; }"));
    125 		}
    126 	}
    127 }
    128 
    129 sub man_output
    130 {
    131 	$_ = shift;
    132 	if(s/^\n//) {
    133 		output "\n" unless $newline_last++;
    134 	}
    135 	return if $_ eq '';
    136 	
    137 	output $_;
    138 
    139 	if(@_) {
    140 		output @_;
    141 		$newline_last = (pop(@_) =~ /\n$/);
    142 	} else {
    143 		$newline_last = ($_ =~ /\n$/)
    144 	}
    145 }
    146 
    147 # Fold lines into one, quote some characters
    148 sub fold_string
    149 {
    150 	$_ = shift;
    151 	
    152 	s/\\/\\\\/g;
    153 	s/"/\\\&"/g;
    154 
    155 	# Change tabs to spaces
    156 	tr/\t\n/  /;
    157 
    158 	# Trim whitespace from beginning and end.
    159 	s/^ +//;
    160 	s/ +$//;
    161 
    162 	return $_;
    163 }
    164 	
    165 sub save_cdata()
    166 {
    167 	$raw_cdata++;
    168 	push_output('string');
    169 }
    170 
    171 sub bold_on()
    172 {
    173 	# If the last font is also bold, don't change anything.
    174 	# Basically this is to just get more readable man output.
    175 	if($fontstack[$#fontstack] ne 'bold') {
    176 		if(!$raw_cdata) {
    177 			output '\fB';
    178 			$newline_last = 0;
    179 		}
    180 	}
    181 	push(@fontstack, 'bold');
    182 }
    183 
    184 sub italic_on()
    185 {
    186 	# If the last font is also italic, don't change anything.
    187 	if($fontstack[$#fontstack] ne 'italic') {
    188 		if(!$raw_cdata) {
    189 			output '\fI';
    190 			$newline_last = 0;
    191 		}
    192 	}
    193 	push(@fontstack, 'italic');
    194 }
    195 
    196 sub font_off()
    197 {
    198 	my $thisfont = pop(@fontstack);
    199 	my $lastfont = $fontstack[$#fontstack];
    200 	
    201 	# Only output font change if it is different
    202 	if($thisfont ne $lastfont) {
    203 		if($raw_cdata)			{ return; }
    204 		elsif($lastfont eq 'bold') 	{ output '\fB'; }
    205 		elsif($lastfont eq 'italic')	{ output '\fI'; }
    206 		else				{ output '\fR'; }
    207 	
    208 		$newline_last = 0;
    209 	}
    210 }
    211 
    212 
    213 
    214 
    215 
    216 
    217 ########################################################################
    218 #
    219 # Manpage management
    220 #
    221 ########################################################################
    222 
    223 sgml('<REFENTRY>', sub { 
    224 	# This will be overwritten at end of REFMETA, when we know the name of the page.
    225 	pop_output();
    226 	
    227 	$write_manpages = 1;		# Currently writing manpage.
    228 	
    229 	$nocollapse_whitespace = 0;	# Current whitespace collapse counter.
    230 	$newline_last = 1;		# At beginning of line?
    231 		# Just a bit of warning, you will see this variable manipulated
    232 		# manually a lot.  It makes the code harder to follow but it
    233 		# saves you from having to worry about collapsing at the end of
    234 		# parse, stopping at verbatims, etc.
    235 	$raw_cdata = 0;                 # Instructs certain output functions to
    236 					# leave CDATA alone, so we can assign
    237 					# it to a string and process it, etc.
    238 	@fontstack = ();		# Fonts being activated.
    239 	
    240 	$manpage_title = '';		# Needed for indexing.
    241 	$manpage_sect = '';
    242 	@manpage_names = ();
    243 	
    244 	$manpage_misc = '';
    245 	
    246 	$list_nestlevel = 0;		# Indent certain nested content.
    247 });
    248 sgml('</REFENTRY>', sub {
    249 	if(!$newline_last) {
    250 		output "\n";
    251 	}
    252 	
    253 	$write_manpages = 0;
    254 	$raw_cdata = 1;
    255 	push_output('nul');
    256 });
    257 
    258 sgml('</REFMETA>', sub {
    259 	push_output('file', "$manpage_title.$manpage_sect");
    260 
    261 	output <<_END_BANNER;
    262 .\\" This manpage has been automatically generated by docbook2man 
    263 .\\" from a DocBook document.  This tool can be found at:
    264 .\\" <http://shell.ipoline.com/~elmert/comp/docbook2X/> 
    265 .\\" Please send any bug reports, improvements, comments, patches, 
    266 .\\" etc. to Steve Cheng <steve\@ggi-project.org>.
    267 _END_BANNER
    268 
    269 	my $manpage_date = `date "+%d %B %Y"`;
    270 		
    271 	output '.TH "';
    272 	
    273 	# If the title is not mixed-case, convention says to
    274 	# uppercase the whole title.  (The canonical title is
    275 	# lowercase.)
    276 	if($manpage_title =~ /[A-Z]/) {
    277 		output fold_string($manpage_title);
    278 	} else {
    279 		output uc(fold_string($manpage_title));
    280 	}
    281 	
    282 	output  '" "', fold_string($manpage_sect), 
    283 		'" "', fold_string(`date "+%d %B %Y"`), 
    284 		'" "', $manpage_misc, 
    285 		'" "', $manpage_manual, 
    286 		"\"\n";
    287 
    288 	$newline_last = 1;
    289 
    290 	# References to this RefEntry.
    291 	my $id = $_[0]->parent->attribute('ID')->value;
    292 	if($id ne '') {
    293 		# The 'package name' part of the section should
    294 		# not be used when citing it.
    295 		my ($sectnum) = ($manpage_sect =~ /([0-9]*)/);
    296 		
    297 		if($_[0]->parent->attribute('XREFLABEL')->value eq '') {
    298 			$Refs->put("refentry:$id", "$manpage_title($sectnum)");
    299 		} else {
    300 			$Refs->put("refentry:$id",
    301 				$_[0]->parent->attribute('XREFLABEL')->value . 
    302 				"($sectnum)");
    303 		}
    304 	}
    305 });
    306 
    307 sgml('<REFENTRYTITLE>', sub { 
    308 	if($_[0]->in('REFMETA')) { 
    309 		save_cdata();
    310 	} else { 
    311 		# Manpage citations are in bold.
    312 		bold_on();
    313 	}
    314 });
    315 sgml('</REFENTRYTITLE>', sub { 
    316 	if($_[0]->in('REFMETA')) {
    317 		$raw_cdata--;
    318 		$manpage_title = pop_output();
    319 	}
    320 	else { font_off(); }
    321 });
    322 
    323 sgml('<MANVOLNUM>', sub { 
    324 	if($_[0]->in('REFMETA')) { 
    325 		save_cdata();	
    326 	} else {
    327 		# Manpage citations use ().
    328 		output '(';
    329 	}
    330 });
    331 sgml('</MANVOLNUM>', sub { 
    332 	if($_[0]->in('REFMETA')) {
    333 		$raw_cdata--;
    334 		$manpage_sect = pop_output();
    335 	}
    336 	else { output ')' }
    337 });
    338 
    339 sgml('<REFMISCINFO>', \&save_cdata);
    340 sgml('</REFMISCINFO>', sub { 
    341 	$raw_cdata--;
    342 	$manpage_misc = fold_string(pop_output());
    343 });
    344 
    345 
    346 # NAME section
    347 man_sgml('<REFNAMEDIV>', "\n.SH NAME\n");
    348 
    349 sgml('<REFNAME>', \&save_cdata);
    350 sgml('</REFNAME>', sub { 
    351 	$raw_cdata--;
    352 	push(@manpage_names, pop_output());
    353 });
    354 
    355 sgml('<REFPURPOSE>', \&save_cdata);
    356 sgml('</REFPURPOSE>', sub { 
    357 	$raw_cdata--;
    358 	my $manpage_purpose = fold_string(pop_output());
    359 	
    360 	for(my $i = 0; $i < $#manpage_names; $i++) {
    361 		output fold_string($manpage_names[$i]), ', ';
    362 	}
    363 
    364 	output fold_string($manpage_names[$#manpage_names]);
    365 	output " \\- $manpage_purpose\n";
    366 
    367 	$newline_last = 1;
    368 
    369 	foreach(@manpage_names) {
    370 		# Don't link to itself
    371 		if($_ ne $manpage_title) {
    372 			print LINKSFILE "$manpage_title.$manpage_sect	$_.$manpage_sect\n";
    373 		}
    374 	}
    375 });
    376 	
    377 man_sgml('<REFCLASS>', "\n.sp\n");
    378 
    379 #RefDescriptor
    380 
    381 
    382 
    383 
    384 
    385 ########################################################################
    386 #
    387 # SYNOPSIS section and synopses
    388 #
    389 ########################################################################
    390 
    391 man_sgml('<REFSYNOPSISDIV>', "\n.SH SYNOPSIS\n");
    392 man_sgml('</REFSYNOPSISDIV>', "\n");
    393 
    394 ## FIXME! Must be made into block elements!!
    395 #sgml('<FUNCSYNOPSIS>', \&bold_on);
    396 #sgml('</FUNCSYNOPSIS>', \&font_off);
    397 #sgml('<CMDSYNOPSIS>', \&bold_on);
    398 #sgml('</CMDSYNOPSIS>', \&font_off);
    399 
    400 man_sgml('<FUNCSYNOPSIS>', sub {
    401 	man_output("\n.sp\n");
    402 	bold_on();
    403 });
    404 man_sgml('</FUNCSYNOPSIS>', sub {
    405 	font_off();
    406 	man_output("\n");
    407 });
    408 
    409 man_sgml('<CMDSYNOPSIS>', "\n\n");
    410 man_sgml('</CMDSYNOPSIS>', "\n\n");
    411 
    412 man_sgml('<FUNCPROTOTYPE>', "\n.sp\n");
    413 
    414 # Arguments to functions.  This is C convention.
    415 man_sgml('<PARAMDEF>', '(');
    416 man_sgml('</PARAMDEF>', ");\n");
    417 man_sgml('<VOID>', "(void);\n");
    418 
    419 
    420 
    421 sub 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 }
    435 sub 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 
    448 sgml('<ARG>', \&arg_start);
    449 sgml('</ARG>', \&arg_end);
    450 sgml('<GROUP>', \&arg_start);
    451 sgml('</GROUP>', \&arg_end);
    452 
    453 sgml('<OPTION>', \&bold_on);
    454 sgml('</OPTION>', \&font_off);
    455 
    456 # FIXME: This is one _blank_ line.
    457 man_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.
    468 man_sgml('<REFSECT1>', "\n.SH ");
    469 man_sgml('<REFSECT2>', "\n.SS ");
    470 man_sgml('<REFSECT3>', "\n.SS ");
    471 
    472 
    473 ########################################################################
    474 #
    475 # Titles, metadata.
    476 #
    477 ########################################################################
    478 
    479 sgml('<TITLE>', sub {
    480 	if($_[0]->in('REFERENCE') or $_[0]->in('BOOK')) {
    481 		$write_manpages = 1;
    482 	}
    483 	save_cdata();
    484 });
    485 sgml('</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 
    508 sgml('<ATTRIBUTION>', sub { push_output('string') });
    509 sgml('</ATTRIBUTION>', sub { $_[0]->parent->ext->{'attribution'} = pop_output(); });
    510 
    511 
    512 # IGNORE.
    513 sgml('<DOCINFO>', sub { push_output('nul'); });
    514 sgml('</DOCINFO>', sub { pop_output(); });
    515 sgml('<REFSECT1INFO>', sub { push_output('nul'); });
    516 sgml('</REFSECT1INFO>', sub { pop_output(); });
    517 sgml('<REFSECT2INFO>', sub { push_output('nul'); });
    518 sgml('</REFSECT2INFO>', sub { pop_output(); });
    519 sgml('<REFSECT3INFO>', sub { push_output('nul'); });
    520 sgml('</REFSECT3INFO>', sub { pop_output(); });
    521 
    522 sgml('<INDEXTERM>', sub { push_output('nul'); });
    523 sgml('</INDEXTERM>', sub { pop_output(); });
    524 
    525 
    526 ########################################################################
    527 #
    528 # Set bold on enclosed content 
    529 #
    530 ########################################################################
    531 
    532 sgml('<APPLICATION>', \&bold_on);	sgml('</APPLICATION>', \&font_off);
    533 
    534 sgml('<CLASSNAME>', \&bold_on);		sgml('</CLASSNAME>', \&font_off);
    535 sgml('<STRUCTNANE>', \&bold_on);	sgml('</STRUCTNAME>', \&font_off);
    536 sgml('<STRUCTFIELD>', \&bold_on);	sgml('</STRUCTFIELD>', \&font_off);
    537 sgml('<SYMBOL>', \&bold_on);		sgml('</SYMBOL>', \&font_off);
    538 sgml('<TYPE>', \&bold_on);		sgml('</TYPE>', \&font_off);
    539 
    540 sgml('<ENVAR>', \&bold_on);	sgml('</ENVAR>', \&font_off);
    541 
    542 sgml('<FUNCTION>', \&bold_on);	sgml('</FUNCTION>', \&font_off);
    543 
    544 sgml('<EMPHASIS>', \&bold_on);	sgml('</EMPHASIS>', \&font_off);
    545 
    546 sgml('<ERRORNAME>', \&bold_on);	sgml('</ERRORNAME>', \&font_off);
    547 # ERRORTYPE
    548 
    549 sgml('<COMMAND>', \&bold_on);	sgml('</COMMAND>', \&font_off);
    550 
    551 sgml('<GUIBUTTON>', \&bold_on);	sgml('</GUIBUTTON>', \&font_off);
    552 sgml('<GUIICON>', \&bold_on);	sgml('</GUIICON>', \&font_off);
    553 # GUILABEL
    554 # GUIMENU
    555 # GUIMENUITEM
    556 # GUISUBMENU
    557 # MENUCHOICE
    558 # MOUSEBUTTON
    559 
    560 sgml('<ACCEL>', \&bold_on);	sgml('</ACCEL>', \&font_off);
    561 sgml('<KEYCAP>', \&bold_on);	sgml('</KEYCAP>', \&font_off);
    562 sgml('<KEYSYM>', \&bold_on);	sgml('</KEYSYM>', \&font_off);
    563 # KEYCODE
    564 # KEYCOMBO
    565 # SHORTCUT
    566 
    567 sgml('<USERINPUT>', \&bold_on);	sgml('</USERINPUT>', \&font_off);
    568 
    569 sgml('<INTERFACEDEFINITION>', \&bold_on);
    570 sgml('</INTERFACEDEFINITION>', \&font_off);
    571 
    572 # May need to look at the CLASS
    573 sgml('<SYSTEMITEM>', \&bold_on);
    574 sgml('</SYSTEMITEM>', \&font_off);
    575 
    576 
    577 
    578 
    579 
    580 ########################################################################
    581 #
    582 # Set italic on enclosed content 
    583 #
    584 ########################################################################
    585 
    586 sgml('<FIRSTTERM>', \&italic_on);	sgml('</FIRSTTERM>', \&font_off);
    587 
    588 sgml('<FILENAME>', \&italic_on);	sgml('</FILENAME>', \&font_off);
    589 sgml('<PARAMETER>', \&italic_on);	sgml('</PARAMETER>', \&font_off);
    590 sgml('<PROPERTY>', \&italic_on);	sgml('</PROPERTY>', \&font_off);
    591 
    592 sgml('<REPLACEABLE>', sub {
    593 	italic_on();
    594 	if($_[0]->in('TOKEN')) {
    595 		# When tokenizing, follow more 'intuitive' convention
    596 		output "<";
    597 	}
    598 });
    599 sgml('</REPLACEABLE>', sub {
    600 	if($_[0]->in('TOKEN')) {
    601 		output ">";
    602 	}
    603 	font_off();
    604 });
    605 
    606 sgml('<CITETITLE>', \&italic_on);	sgml('</CITETITLE>', \&font_off);
    607 sgml('<FOREIGNPHRASE>', \&italic_on);	sgml('</FOREIGNPHRASE>', \&font_off);
    608 
    609 sgml('<LINEANNOTATION>', \&italic_on);	sgml('</LINEANNOTATION>', \&font_off);
    610 
    611 
    612 
    613 
    614 
    615 
    616 ########################################################################
    617 #
    618 # Other 'inline' elements 
    619 #
    620 ########################################################################
    621 
    622 man_sgml('<EMAIL>', '<');
    623 man_sgml('</EMAIL>', '>');
    624 man_sgml('<OPTIONAL>', '[');
    625 man_sgml('</OPTIONAL>', ']');
    626 
    627 man_sgml('</TRADEMARK>', "\\u\\s-2TM\\s+2\\d");
    628 
    629 man_sgml('<COMMENT>', "[Comment: ");
    630 man_sgml('</COMMENT>', "]");
    631 
    632 man_sgml('<QUOTE>', "``");
    633 man_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 
    674 sub 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
    697 sub para_end {
    698 	output "\n" unless $newline_last++; 
    699 }
    700 
    701 sgml('<PARA>', \&para_start);
    702 sgml('</PARA>', \&para_end);
    703 sgml('<SIMPARA>', \&para_start);
    704 sgml('</SIMPARA>', \&para_end);
    705 
    706 # Nothing special, except maybe FIXME set nobreak.
    707 sgml('<INFORMALEXAMPLE>', \&para_start);
    708 sgml('</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).
    724 sgml('<ABSTRACT>', sub {
    725 	$_[0]->ext->{'title'} = 'ABSTRACT';
    726 	output "\n" unless $newline_last++;
    727 	push_output('string');
    728 });
    729 sgml('</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.
    741 man_sgml('<EXAMPLE>', "\n.SS ");
    742 sgml('</EXAMPLE>', \&para_end);
    743 
    744 # Same with sidebar.
    745 man_sgml('<SIDEBAR>', "\n.SS ");
    746 sgml('</SIDEBAR>', \&para_end);
    747 
    748 # NO title.
    749 man_sgml('<HIGHLIGHTS>', "\n.SS HIGHLIGHTS\n");
    750 sgml('</HIGHLIGHTS>', \&para_end);
    751 
    752 
    753 
    754 
    755 ########################################################################
    756 #
    757 # Indented 'Block' elements 
    758 #
    759 ########################################################################
    760 
    761 sub indent_block_start
    762 {
    763 	output "\n" unless $newline_last++;
    764 	output ".sp\n.RS\n";
    765 }
    766 sub 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 
    775 sgml('<BLOCKQUOTE>', sub { 
    776 	$_[0]->ext->{'title'} = ''; 
    777 	output "\n" unless $newline_last++;
    778 	push_output('string');
    779 });
    780 sgml('</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.
    803 sub 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 
    822 sgml('<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 });
    834 sgml('</NOTE>', \&admonition_end);
    835 
    836 # Same as above.
    837 sgml('<WARNING>', sub { 
    838 	$_[0]->ext->{'title'} = 'Warning'; 
    839 	output "\n" unless $newline_last++;
    840 	push_output('string');
    841 });
    842 sgml('</WARNING>', \&admonition_end);
    843 
    844 sgml('<TIP>', sub {
    845 	$_[0]->ext->{'title'} = 'Tip';
    846 	output "\n" unless $newline_last++;
    847 	push_output('string');
    848 });
    849 sgml('</TIP>', \&admonition_end);
    850 sgml('<CAUTION>', sub {
    851 	$_[0]->ext->{'title'} = 'Caution';
    852 	output "\n" unless $newline_last++;
    853 	push_output('string');
    854 });
    855 sgml('</CAUTION>', \&admonition_end);
    856 
    857 sgml('<IMPORTANT>', sub {
    858 	$_[0]->ext->{'title'} = 'Important';
    859 	output "\n" unless $newline_last++;
    860 	push_output('string');
    861 });
    862 sgml('</IMPORTANT>', \&admonition_end);
    863 
    864 
    865 
    866 
    867 
    868 
    869 
    870 
    871 
    872 
    873 
    874 
    875 ########################################################################
    876 #
    877 # Verbatim displays. 
    878 #
    879 ########################################################################
    880 
    881 sub 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 
    896 sub verbatim_end {
    897 	output "\n" unless $newline_last++;
    898 	output(".fi\n") unless --$nocollapse_whitespace;
    899 }
    900 
    901 sgml('<PROGRAMLISTING>', \&verbatim_start); 
    902 sgml('</PROGRAMLISTING>', \&verbatim_end);
    903 
    904 sgml('<SCREEN>', \&verbatim_start); 
    905 sgml('</SCREEN>', \&verbatim_end);
    906 
    907 sgml('<LITERALLAYOUT>', \&verbatim_start); 
    908 sgml('</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 #});
    926 sgml('<SYNOPSIS>', \&verbatim_start);
    927 sgml('</SYNOPSIS>', \&verbatim_end);
    928 
    929 
    930 
    931 
    932 
    933 
    934 
    935 
    936 
    937 ########################################################################
    938 #
    939 # Lists
    940 #
    941 ########################################################################
    942 
    943 # Indent nested lists.
    944 sub indent_list_start {
    945 	if($list_nestlevel++) {
    946 		output "\n" unless $newline_last++;
    947 		output ".RS\n";
    948 	}
    949 }
    950 sub indent_list_end {
    951 	if(--$list_nestlevel) {
    952 		output "\n" unless $newline_last++;
    953 		output ".RE\n";
    954 	}
    955 }
    956 
    957 sgml('<VARIABLELIST>', \&indent_list_start);
    958 sgml('</VARIABLELIST>', \&indent_list_end);
    959 sgml('<ITEMIZEDLIST>', \&indent_list_start);
    960 sgml('</ITEMIZEDLIST>', \&indent_list_end);
    961 sgml('<ORDEREDLIST>', sub { 
    962 	indent_list_start();
    963 	$_[0]->ext->{'count'} = 1;
    964 });
    965 sgml('</ORDEREDLIST>', \&indent_list_end);
    966 		
    967 # Output content on one line, bolded.
    968 sgml('<TERM>', sub { 
    969 	output "\n" unless $newline_last++;
    970 	output ".TP\n";
    971 	bold_on();
    972 	push_output('string');
    973 });
    974 sgml('</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 	
    983 sgml('<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 
   1000 sgml('<SIMPLELIST>', sub {
   1001 	$_[0]->ext->{'first_member'} = 1;
   1002 });
   1003 
   1004 sgml('<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
   1059 sgml('</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.
   1066 sgml('<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 
   1095 man_sgml('|[lt    ]|', '<');
   1096 man_sgml('|[gt    ]|', '>');
   1097 man_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 
   1111 sgml('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 
   1147 sgml('re', sub
   1148 {
   1149 	if($nocollapse_whitespace || !$newline_last) {
   1150 		output "\n";
   1151 	}
   1152 
   1153 	$newline_last = 1;
   1154 });
   1155 
   1156 sgml('sdata',sub { die "Unknown SDATA: " . $_[0]; });
   1157 sgml('pi',sub { die "Unknown processing instruction: " . $_[0]; });
   1158 sgml('entity',sub { die "Unknown external entity: " . $_[0]->name; });
   1159 sgml('start_subdoc',sub { die "Unknown subdoc entity: " . $_[0]->name; });
   1160 sgml('end_subdoc','');
   1161 sgml('conforming','');
   1162 
   1163 1;
   1164 
   1165