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