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>', \¶_start); 702sgml('</PARA>', \¶_end); 703sgml('<SIMPARA>', \¶_start); 704sgml('</SIMPARA>', \¶_end); 705 706# Nothing special, except maybe FIXME set nobreak. 707sgml('<INFORMALEXAMPLE>', \¶_start); 708sgml('</INFORMALEXAMPLE>', \¶_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>', \¶_end); 743 744# Same with sidebar. 745man_sgml('<SIDEBAR>', "\n.SS "); 746sgml('</SIDEBAR>', \¶_end); 747 748# NO title. 749man_sgml('<HIGHLIGHTS>', "\n.SS HIGHLIGHTS\n"); 750sgml('</HIGHLIGHTS>', \¶_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