Lorenzo Colitti | 313379e | 2013-07-11 01:07:11 +0900 | [diff] [blame] | 1 | =head1 NAME |
| 2 | |
| 3 | docbook2man-spec - convert DocBook RefEntries to Unix manpages |
| 4 | |
| 5 | =head1 SYNOPSIS |
| 6 | |
| 7 | The SGMLSpm package from CPAN. This contains the sgmlspl script which |
| 8 | is used to grok this file. Use it like this: |
| 9 | |
| 10 | nsgmls some-docbook-document.sgml | sgmlspl docbook2man-spec.pl |
| 11 | |
| 12 | =head1 DESCRIPTION |
| 13 | |
| 14 | This is a sgmlspl spec file that produces Unix-style |
| 15 | manpages from RefEntry markup. |
| 16 | |
| 17 | See the accompanying RefEntry man page for 'plain new' documentation. :) |
| 18 | |
| 19 | =head1 LIMITATIONS |
| 20 | |
| 21 | Trying docbook2man on non-DocBook or non-conformant SGML results in |
| 22 | undefined behavior. :-) |
| 23 | |
| 24 | This program is a slow, dodgy Perl script. |
| 25 | |
| 26 | This program does not come close to supporting all the possible markup |
| 27 | in DocBook, and will produce wrong output in some cases with supported |
| 28 | markup. |
| 29 | |
| 30 | =head1 TODO |
| 31 | |
| 32 | Add new element handling and fix existing handling. Be robust. |
| 33 | Produce cleanest, readable man output as possible (unlike some |
| 34 | other converters). Follow Linux man(7) convention. |
| 35 | If this results in added logic in this script, |
| 36 | that's okay. The code should still be reasonably organized. |
| 37 | |
| 38 | Make it faster. If Perl sucks port it to another language. |
| 39 | |
| 40 | =head1 COPYRIGHT |
| 41 | |
| 42 | Copyright (C) 1998-1999 Steve Cheng <steve@ggi-project.org> |
| 43 | |
| 44 | This program is free software; you can redistribute it and/or modify it |
| 45 | under the terms of the GNU General Public License as published by the Free |
| 46 | Software Foundation; either version 2, or (at your option) any later |
| 47 | version. |
| 48 | |
| 49 | You should have received a copy of the GNU General Public License along with |
| 50 | this program; see the file COPYING. If not, please write to the Free |
| 51 | Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. |
| 52 | |
| 53 | =cut |
| 54 | |
| 55 | # $Id: docbook2man-spec.pl,v 1.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 | |