#!/usr/bin/perl
#
# With some changes from Jochen Topf <jochen@remote.org>
#
# vim:sw=4 sta showmatch
=head1 NAME
docbook2man - convert DocBook documents to man pages
=head1 SYNOPSIS
docbook2man [xml-document...]
=head1 DESCRIPTION
This Perl script converts DocBk XML documents to one or more
man pages.
If no files are specified, docbook2man reads from standard input.
See accompanying DocBook documentation for more details.
=head1 LIMITATIONS
Trying docbook2man on non-DocBook-conformant XML results in
undefined behavior. :-)
=head1 COPYRIGHT
Copyright (C) 1999 Steve Cheng <steve@ggi-project.org>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
$Id: docbook2man,v 1.3 2001/03/30 14:54:17 sqrt Exp $
=cut
package docbook2man;
use lib '..';
use strict;
no strict 'subs';
use XML::DOM;
use XML::DOM::Map;
sub unexpected_node {
my $node = shift;
node_warn $node, "Unexpected node encountered";
}
sub unimplemented_node {
my $node = shift;
node_warn $node, "Tranformation unimplemented";
}
##################################################
#
# Escape/collapse strings for -man.
#
##################################################
sub fold_string {
local $_ = shift;
tr/ \t/ /s;
# Kill spaces at beginning of lines
s/^ +//mg;
tr/\n/\n/s;
# Trim newlines at beginning and end
s/^\n+//;
s/\n+$//;
return $_;
}
sub tr_escape_string {
local $_ = shift;
s/\\/\\\\/g;
s/\'/\\\&'/g;
s/\./\\\&./g;
return $_;
}
@docbook2man::fontstack = ( 'roman' );
sub italic_on() {
if($docbook2man::fontstack[$#docbook2man::fontstack]
ne 'bold')
{
push(@docbook2man::fontstack, 'bold');
return '\fB';
}
else {
push(@docbook2man::fontstack, 'bold');
return '';
}
}
sub bold_on() {
if($docbook2man::fontstack[$#docbook2man::fontstack]
ne 'bold')
{
push(@docbook2man::fontstack, 'bold');
return '\fB';
}
else {
push(@docbook2man::fontstack, 'bold');
return '';
}
}
sub font_off() {
my $thisfont = pop(@docbook2man::fontstack);
my $lastfont = $docbook2man::fontstack[$#docbook2man::fontstack];
if($thisfont eq $lastfont) { return '' }
elsif($lastfont eq 'bold') { return '\fB' }
elsif($lastfont eq 'italic') { return '\fB' }
elsif($lastfont eq 'roman') { return '\fR' }
else { return '' }
}
# Make a roff request, with proper argument handling
# Note that in some places we still use print for this,
# those are FIXME.
sub man_req {
print shift;
foreach (@_) {
my $x = $_;
$x =~ tr/\n"/ '/;
print " \"$x\"";
}
print "\n";
}
##################################################
#
# Handle inline elements/mixed content
#
##################################################
local $docbook2man::inline_map = {
# Ideally, typeset in fixed-width.
# 'elem:classname' => \&tr_inline_code,
# 'elem:command' => \&tr_inline_code,
# 'elem:envar' => \&tr_inline_code,
# 'elem:function' => \&tr_inline_code,
'elem:command' => \&tr_inline_bold,
'elem:function' => \&tr_inline_bold,
'elem:symbol' => \&tr_inline_bold,
'elem:type' => \&tr_inline_bold,
'elem:structname' => \&tr_inline_bold,
# 'elem:parameter' => \&tr_inline_code,
# 'elem:returnvalue' => \&tr_inline_code,
# 'elem:structfield' => \&tr_inline_code,
# 'elem:structname' => \&tr_inline_code,
# 'elem:symbol' => \&tr_inline_code,
# 'elem:type' => \&tr_inline_code,
#
# 'elem:literal' => \&tr_inline_samp,
# 'elem:markup' => \&tr_inline_samp,
# 'elem:sgmltag' => \&tr_inline_samp,
# 'elem:token' => \&tr_inline_samp,
#
# 'elem:option' => \&tr_inline_samp,
# 'elem:prompt' => \&tr_inline_samp,
#
# 'elem:email' => \&tr_inline_email,
#
# 'elem:userinput' => \&tr_inline_kbd,
'elem:replaceable' => \&tr_inline_italic,
# 'elem:citation' => \&tr_inline_italic,
'elem:citetitle' => \&tr_inline_italic,
'elem:firstterm' => \&tr_inline_italic,
'elem:filename' => \&tr_inline_italic,
'elem:foreignphrase' => \&tr_inline_italic,
# 'elem:acronym' => \&tr_inline_small,
'elem:emphasis' => \&tr_inline_bold,
# 'elem:accel' => \&tr_inline_bold,
# 'elem:keycap' => \&tr_inline_bold,
# 'elem:keysym' => \&tr_inline_bold,
'elem:keycombo' => sub {
my $data = '';
foreach(shift->getChildNodes()) {
if($_->getNodeType == ELEMENT_NODE) {
$data .= '+' if defined $data; # Separator between keys
$data .= tr_inline_container($_);
}
}
return $data . '}';
},
'elem:inlinegraphic' => sub {
my $node = shift;
return ' [Graphic: ' .
tr_escape_string($node->getAttribute('fileref')) .
"] ";
},
'elem:citerefentry' => \&db_citerefentry,
# Suppress in final version
'elem:comment' => sub { },
'elem:author' => \&db_author,
'elem:ulink' => \&db_ulink,
'elem:link' => \&db_link,
'elem:xref' => \&db_xref,
'elem:quote' => sub { return '\(lq' . &tr_inline_container . '\(rq' },
'text' => \&tr_cdata,
# Catch-all for unknown elements
'elem' => \&tr_inline_container,
'' => sub {}
};
sub tr_inline_bold {
# I'm a C programmer, there's no guaranteed order of evaluation
# Don't know about Perl
my $f = bold_on();
return $f . &tr_inline_container . font_off();
}
sub tr_inline_italic {
my $f = italic_on();
return $f . &tr_inline_container . font_off();
}
sub tr_inline_roman {
warn "tr_inline_roman unimplemented\n";
&tr_inline_container;
}
sub db_ulink {
my $node = shift;
return tr_inline_container($node) . ' <URL:' .
tr_escape_string($node->getAttribute('url')) . '>';
}
sub db_link {
my $node = shift;
my $xrefnode = getElementByID($node->getAttribute('linkend'));
my $text = tr_inline_container_fold($node);
# Get man page reference that is/contains target node.
my $cnode = $xrefnode;
my $cite;
warn "C Enter loop - ", $cnode->getAttribute('xmanpage'),
defined($cnode->getAttribute('xmanpage'))?1:0, " - \n";
while(!defined ($cite = $cnode->getAttribute('xmanpage'))) {
warn "!defined \$cite\n";
if(!defined ($cnode = $cnode->getParentNode())) {
warn "!defined \$cnode - ", $node->getAttribute('linkend'),
"- \n";
$cite = $node->getAttribute('linkend');
last;
}
}
warn "C exit loop $cite\n";
$cite = tr_escape_string($cite);
$text .= " [Link to: $cite]";
return $text;
}
sub db_xref {
my $node = shift;
my $xrefnode = getElementByID($node->getAttribute('linkend'));
my $text;
if($node->getAttribute('endterm')) {
# Get text from element pointed by endterm
#
# "Pointing to a large structure with ENDTERM is a bad
# idea. ENDTERM should point to something small like a title."
# -norm
# So we don't handle it.
my $endterm = getElementByID($node->getAttribute('endterm'));
$text = tr_inline_container($endterm);
}
elsif(defined $xrefnode->getAttribute('xreflabel')) {
# Use text from xreflabel if endterm not present
$text = tr_escape_string($xrefnode->getAttribute('xreflabel'));
}
else {
# Use target title as text.
$text = tr_inline_container_fold(node_title($xrefnode));
}
# Now for the 'link' itself:
# (In man, a 'link' is just a textual pointer.
# Thus endterm/xreflabel content cannot replace it, as with normal
# implementations)
# Get man page reference that is/contains target node.
my $cnode = $xrefnode;
my $cite;
while(!defined ($cite = $cnode->getAttribute('xmanpage'))) {
if(!defined ($cnode = $cnode->getParentNode())) {
$cite = $node->getAttribute('linkend');
last;
}
}
$cite = tr_escape_string($cite);
$text .= " [XRef to: $cite] ";
return $text;
}
sub db_citerefentry {
my $node = shift;
my $cdata = '';
foreach($node->getChildNodes())
{
$cdata .= node_map($_,
{
'elem:refentrytitle' => \&tr_inline_container,
'elem:manvolnum' => sub {
return '(' . &tr_inline_container . ')'; },
'whitespace' => sub {},
'elem' => \&unexpected_node,
'text' => \&unexpected_node,
'' => sub {},
});
}
return $cdata;
}
# Space between elements and
sub db_author {
my $cdata = '';
foreach(shift->getChildNodes) {
$cdata .= node_map($_, {
'elem:affiliation' => sub { return '(' .
tr_inline_container(shift) . ') ' },
'elem:authorblurb' => sub { },
'elem:contrib' => sub { return '(' .
tr_inline_container(shift) . ') ' },
'elem' => sub {
tr_inline_container(shift) . ' ' },
'whitespace' => sub {},
'text' => \&unexpected_node,
'' => sub {},
});
}
return $cdata;
}
# Return all character data (and only character data)
# from specified node and below.
#
sub tr_cdata {
my $node = shift;
# We can ignore node_map spec here.
if($node->getNodeType == TEXT_NODE) {
return tr_escape_string($node->getData());
} elsif($node->getNodeType == ELEMENT_NODE) {
my $cdata = '';
# Inlines always contain character data
# or inline, so we're ok.
foreach($node->getChildNodes()) {
$cdata .= tr_cdata($_);
}
return $cdata;
} else {
return '';
}
}
# Handles inline elements, and squeeze into one line.
# The difference from tr_para is that it ignores block elements.
#
sub tr_inline_container {
my $node = shift;
my $text = '';
foreach($node->getChildNodes()) {
$text .= node_map($_, $docbook2man::inline_map);
}
return $text;
}
sub tr_inline_container_fold {
my $cdata = &tr_inline_container;
return fold_string($cdata);
}
##################################################
#
# Handle block-oriented elements.
#
##################################################
local $docbook2man::block_map = {
'elem:para' => \&db_para,
'elem:formalpara' => \&tr_block_container_with_title,
'elem:simpara' => \&db_para,
'elem:blockquote' => \&db_blockquote,
'elem:example' => \&tr_block_container_with_title,
'elem:informalexample' => \&tr_block_container,
'elem:address' => \&tr_verbatim,
'elem:itemizedlist' => \&db_itemizedlist,
'elem:variablelist' => \&db_variablelist,
'elem:orderedlist' => \&db_orderedlist,
'elem:simplelist' => \&db_simplelist,
'elem:programlisting' => \&tr_verbatim,
'elem:screen' => \&tr_verbatim,
'elem:literallayout' => \&tr_verbatim,
'elem:caution' => sub {
tr_block_container_with_title(shift, "Caution") },
'elem:important' => sub {
tr_block_container_with_title(shift, "Important") },
'elem:note' => sub {
tr_block_container_with_title(shift, "Note") },
'elem:tip' => sub {
tr_block_container_with_title(shift, "Tip") },
'elem:warning' => sub {
tr_block_container_with_title(shift, "Warning") },
'elem:funcsynopsis' => \&db_funcsynopsis,
'elem:graphic' => sub {
my $node = shift;
print '[Graphic: ',
fold_string(tr_escape_string($node->getAttribute('fileref'))),
"]\n\n";
},
'whitespace' => sub {},
'elem' => sub { die NMOC_NEXTSPEC_REDO },
'text' => \&unexpected_node,
'' => sub {},
};
sub is_block {
my $node = shift;
return ($node->getNodeType == ELEMENT_NODE and
exists $docbook2man::block_map->{'elem:' . $node->getTagName()});
}
sub tr_block_container_with_title {
my $node = shift;
my $title = shift;
my $numblocks = 0;
foreach($node->getChildNodes()) {
$numblocks++ if is_block($_);
};
# If one block only, looks nicer if title is inline with it.
if($numblocks==1) {
local $docbook2man::use_separator;
node_map_ordered_children($node,
{
'elem:title' => sub {
print fold_string(tr_inline_bold(shift)), ": \n";
$docbook2man::use_separator = "\n\n";
die NMOC_NEXTSPEC; },
'whitespace' => sub {},
'elem' => sub {
if($docbook2man::use_separator) {
man_req('.sp');
} else {
man_req('.PP');
}
$docbook2man::use_separator = "\n\n";
man_req('.B', $title . ': ') if defined $title;
die NMOC_NEXTSPEC_REDO },
'' => sub {}
},
$docbook2man::block_map);
}
else {
node_map_ordered_children($node,
{
'elem:title' => sub {
print fold_string(tr_inline_bold(shift)), "\n\n";
die NMOC_NEXTSPEC; },
'whitespace' => sub {},
'elem' => sub {
if($docbook2man::use_separator) {
man_req('.sp');
} else {
man_req('.PP');
}
if(defined $title) {
man_req('.B', $title);
man_req('.br');
}
die NMOC_NEXTSPEC_REDO },
'' => sub {}
},
$docbook2man::block_map);
}
}
sub tr_block_container {
my $node = shift;
my $count = 0;
foreach($node->getChildNodes()) {
if(is_block($_)) {
if(defined $docbook2man::use_separator and $count++) {
# Print blank line to separate blocks.
print "\n";
}
node_map($_, $docbook2man::block_map);
} else {
# FIXME !is_block is not necessarily an error.
#unexpected_node($_);
}
}
}
sub tr_verbatim
{
print ".sp\n";
print ".nf\n";
foreach(shift->getChildNodes) {
print node_map($_, $docbook2man::inline_map);
}
print "\n.fi\n";
}
sub db_blockquote {
my $node = shift;
man_req('.RS');
tr_block_container_with_title($node);
# Print attribution.
node_map_ordered_children($node,
{
'elem:attribution' => sub {
print ' ' x 20, '\(em ';
print &tr_inline_container_fold;
print "\n" },
'' => sub {}
});
man_req('.RE');
}
# Para allows mixed in-line and block elements, so if we encounter
# a nested block, we must start a 'new paragraph' for the in-line
# content after it.
#
sub db_para {
my $node = shift;
my $text = '';
my $noindent = 0;
if(!defined $docbook2man::use_separator) {
man_req('.PP');
}
foreach($node->getChildNodes()) {
if(is_block($_)) {
$text = fold_string($text);
if($text ne '') {
print "\n\n" if $noindent;
print $text, "\n";
$text = '';
}
$noindent = 1;
node_map($_, $docbook2man::block_map);
} else {
$text .= node_map($_, $docbook2man::inline_map);
}
}
$text = fold_string($text);
if($text ne '') {
man_req('.PP') if $noindent;
print $text, "\n";
}
}
# We transform listitem into a hanging tag.
# .PP brings it back, so we have to override default db_para
# with db_para_bs.
sub db_listitem
{
my ($node, $tag, $arg) = @_;
# Lets not worry about roff's stupid quoting rules...
# if(defined $arg) {
# if(defined $tag) {
# $tag =~ tr/\n/ /;
# print ".TP $arg\n$tag\n";
# }
# } else {
# if(defined $tag) {
# $tag =~ tr/\n/ /;
# print ".IP $tag\n";
# }
# }
$tag =~ tr/\n/ /;
if(defined $tag or defined $arg) {
print ".TP $arg\n$tag\n";
}
local $docbook2man::use_separator = "\n\n";
tr_block_container($node);
}
sub db_simplelist {
my $node = shift;
# FIXME This should be part of the enclosing paragraph;
# thus both a 'block' and 'inline' element.
if($node->getAttribute('type') eq 'inline') {
node_map_ordered_children($node,
{
'elem:member' => sub {
print &tr_inline_container_fold;
die NMOC_NEXTSPEC;
},
'whitespace' => sub {},
'elem' => \&unexpected_node,
'text' => \&unexpected_node,
'' => sub {},
},
{
'elem:member' => sub {
print ', ', &tr_inline_container_fold; },
'whitespace' => sub {},
'elem' => \&unexpected_node,
'text' => \&unexpected_node,
'' => sub {},
});
print "\n\n";
} else {
# FIXME: type=horiz/vert, columns=x
node_map_ordered_children($node,
{
'elem:member' => sub {
man_req('.TP', '0.2i');
print "\\&\n";
print &tr_inline_container_fold;
print "\n"; },
'whitespace' => sub {},
'elem' => \&unexpected_node,
'text' => \&unexpected_node,
'' => sub {},
});
}
}
sub db_itemizedlist
{
my $node = shift;
# FIXME Allow other mark. DocBook says there's no fixed
# list of these.
node_map_ordered_children($node,
{
'elem:listitem' => sub { db_listitem(shift, '\(bu', "0.2i"); },
'whitespace' => sub {},
'elem' => \&unexpected_node,
'text' => \&unexpected_node,
'' => sub {},
});
}
sub db_orderedlist
{
my $node = shift;
# FIXME: Different enumerate styles
local $docbook2man::enumerate_count = 1;
node_map_ordered_children($node,
{
'elem:listitem' => sub {
db_listitem(shift, $docbook2man::enumerate_count++); },
'whitespace' => sub {},
'elem' => \&unexpected_node,
'text' => \&unexpected_node,
'' => sub {},
});
}
sub db_variablelist
{
my $node = shift;
node_map_ordered_children($node,
{
'elem:varlistentry' => sub {
node_map_ordered_children(shift,
{
'elem:term' => sub {
my $term = &tr_inline_container_fold;
$term =~ tr/\n/ /;
man_req('.TP');
print "$term\n";
die NMOC_NEXTSPEC; },
'whitespace' => sub {},
'elem:listitem' => sub {
db_listitem(shift); },
'elem' => \&unexpected_node,
'text' => \&unexpected_node,
'' => sub {},
});
},
'whitespace' => sub {},
'elem' => \&unexpected_node,
'text' => \&unexpected_node,
'' => sub {},
});
}
##################################################
#
# Synopses.
#
##################################################
sub db_funcsynopsis {
node_map_ordered_children(shift,
{
'elem:funcsynopsisinfo' => sub {
my $node = shift;
if($node->getAttribute('format') eq 'linespecific')
{
foreach($node->getChildNodes) {
print node_map($_, $docbook2man::inline_map);
}
} else {
print tr_inline_container($node);
}
print "\n\n";
},
# Both the specification and the implementation
# for FuncSynopsis sucks.
'elem:funcprototype' => sub {
node_map_ordered_children(shift,
{
'elem:funcdef' => sub {
print &tr_cdata, '(' ; },
'elem:void' => sub { print 'void' },
'elem:varargs' => sub { print '...'; },
'elem:paramdef' => sub {
print &tr_inline_container; },
'whitespace' => sub {},
'elem' => \&unexpected_node,
'text' => \&unexpected_node,
'' => sub {},
});
print ");\n\n";
},
'whitespace' => sub {},
'elem' => \&unexpected_node,
'text' => \&unexpected_node,
'' => sub {},
});
}
##################################################
#
# General sections.
#
##################################################
local $docbook2man::sect_map = {
'elem:preface' => \&tr_section,
'elem:chapter' => \&tr_section,
'elem:sect1' => \&tr_section,
'elem:sect2' => \&tr_section,
'elem:sect3' => \&tr_section,
'elem:sect4' => \&tr_section,
'elem:sect5' => \&tr_section,
'elem:simplesect' => \&tr_section,
'elem:article' => \&tr_section,
'elem:refentry' => \&tr_section,
# 'elem:refentry' => \&tr_refentry_top,
'elem:refnamediv' => \&db_refnamediv,
'elem:refsynopsisdiv' => \&db_refsynopsisdiv,
'elem:refsect1' => \&tr_section,
'elem:refsect2' => \&tr_section,
'elem:refsect3' => \&tr_section,
'elem:reference' => \&tr_section,
#FIXME handle part element, abstract, sidebar, bridgehead
'' => sub {}
};
sub is_section {
my $node = shift;
return ($node->getNodeType == ELEMENT_NODE and
exists $docbook2man::sect_map->{'elem:' . $node->getTagName()});
}
$docbook2man::section_counter = 0;
sub tr_section
{
my $node = shift;
my $title = tr_inline_container_fold(node_title($node));
if(++$docbook2man::section_counter==1) {
$node->setAttribute('xmanpage', $title);
man_req('.TH', $title,
$docbook2man::default_section,
$docbook2man::default_misc,
$docbook2man::default_date,
$docbook2man::default_manual);
} elsif($docbook2man::section_counter==2) {
$title = uc($title);
man_req('.SH', $title);
} elsif($docbook2man::section_counter==3) {
man_req('.SS', $title);
} elsif($docbook2man::section_counter==4) {
man_req('.PP');
man_req('.B', $title);
man_req('.br');
} else {
man_req('.PP');
man_req('.B', $title);
man_req('.br');
node_warn $node, "Section too deep, mapped to lowest.\n";
}
node_map_ordered_children($node,
$docbook2man::docinfo_map,
$docbook2man::secttoc_map,
$docbook2man::block_map,
$docbook2man::sect_map);
$docbook2man::section_counter--;
}
# Same as tr_section, except TH header components are
# explicitly specified.
#
sub tr_section_manpage
{
my ($node, $title, $sect, $date, $misc, $manual) = @_;
if(++$docbook2man::section_counter!=1) {
die "tr_section_manpage called as non-top section!?\n";
}
man_req('.TH', $title, $sect, $date, $misc, $manual);
node_map_ordered_children($node,
$docbook2man::docinfo_map,
$docbook2man::secttoc_map,
$docbook2man::block_map,
$docbook2man::sect_map);
$docbook2man::section_counter--;
}
##################################################
#
# Reference pages.
#
##################################################
# We want a separate man page-- usually
sub tr_refentry_top {
my $node = shift;
my @citeinfo;
local $docbook2man::section_counter=0;
foreach($node->getChildNodes()) {
if($_->getNodeType == ELEMENT_NODE and
$_->getTagName() eq 'refmeta')
{
@citeinfo = refmeta_get_cite($_);
}
}
if(!@citeinfo) {
# FIXME move this somewhere else...
@citeinfo = ( tr_inline_container_fold(node_title($node)),
$docbook2man::default_section,
$docbook2man::default_misc );
} else {
# Truncate additional miscinfo stuff.
$#citeinfo = 2;
}
tr_section_manpage($node, @citeinfo, 'date-placeholder', 'manual??');
}
sub db_refnamediv {
my $node = shift;
if(++$docbook2man::section_counter==2) {
# Not a typo. Standalone RefEntry-manpages should have
# capitalized headings.
man_req('.SH', 'NAME');
}
elsif($docbook2man::section_counter==3) {
man_req('.SS', 'Name');
}
elsif($docbook2man::section_counter==4) {
man_req('.PP');
man_req('.B', 'Name');
man_req('.br');
}
else {
node_warn($node, "Section too deep, mapped to lowest.\n");
man_req('.PP');
man_req('.B', 'Name');
man_req('.br');
}
node_map_ordered_children($node,
{
'elem:refdescriptor' => sub {}, # FIXME
'elem:refname' => sub {
print &tr_inline_container_fold;
die NMOC_NEXTSPEC; },
'whitespace' => sub {},
'' => sub { &unexpected_node; die NMOC_NEXTSPEC_REDO; }
},
{
'elem:refname' => sub {
print ', ', &tr_inline_container_fold; },
'elem:refpurpose' => sub {
print ' \- ', &tr_inline_container_fold; },
'' => sub {}
});
print "\n";
$docbook2man::section_counter--;
}
sub db_arg {
my $node = shift;
node_map_ordered_children($node,
{
'elem:option' => sub {
print '[\\' . &tr_inline_container_fold . "]\n";
},
'elem:replaceable' => sub {
print &tr_inline_container_fold . "\n";
},
'whitespace' => sub {},
'text' => \&unexpected_node,
'' => sub {},
},
$docbook2man::block_map);
}
sub db_cmdsynopsis {
my $node = shift;
# my $section;
# if(++$docbook2man::section_counter==2) { $section = '.SH'; }
# elsif($docbook2man::section_counter==3) { $section = '.SS'; }
# elsif($docbook2man::section_counter==4) { $section = ".PP\n.B"; }
# else {
# node_warn $node, "Section too deep, mapped to lowest.\n";
# $section = ".PP\n.B";
# }
node_map_ordered_children($node,
{
'elem:command' => sub {
man_req('.SH', "SYNOPSIS");
man_req('.B', &tr_inline_container_fold);
},
'elem:arg' => \&db_arg,
'whitespace' => sub {},
'text' => \&unexpected_node,
'' => sub {},
},
$docbook2man::block_map);
}
sub db_refsynopsisdiv {
my $node = shift;
my $section;
if(++$docbook2man::section_counter==2) { $section = '.SH'; }
elsif($docbook2man::section_counter==3) { $section = '.SS'; }
elsif($docbook2man::section_counter==4) { $section = ".PP\n.B"; }
else {
node_warn $node, "Section too deep, mapped to lowest.\n";
$section = ".PP\n.B";
}
node_map_ordered_children($node,
{
'elem:cmdsynopsis' => \&db_cmdsynopsis,
'elem:refsynopsisinfo' => \&tr_docinfo,
'elem:title' => sub {
my $title = &tr_inline_container_fold;
$title = uc($title) if $section eq '.SH';
man_req($section, $title);
die NMOC_NEXTSPEC },
'whitespace' => sub {},
'text' => \&unexpected_node,
'elem' => sub {
if($section eq '.SH') {
man_req($section, "SYNOPSIS");
} else {
man_req($section, "Synopsis");
}
die NMOC_NEXTSPEC_REDO;
},
'' => sub {},
},
$docbook2man::block_map);
$docbook2man::section_counter--;
}
# Get RefMeta content conveniently in a list.
# Used for manpage citations.
#
sub refmeta_get_cite {
my $node = shift;
my @result;
my $manvolnum_seen = 0;
foreach($node->getChildNodes()) {
# Assume RefEntryTitle,ManVolNum?,RefMiscInfo*
if($_->getNodeType == ELEMENT_NODE) {
# Handle optional manvolnum.
if($_->getTagName() eq 'manvolnum') {
$manvolnum_seen = 1;
} elsif(!$manvolnum_seen and
$_->getTagName() eq 'refmiscinfo') {
$manvolnum_seen = 1;
push(@result, $docbook2man::default_section);
}
push(@result, fold_string(tr_cdata($_)));
}
}
return @result;
}
##################################################
#
# Handle metadata.
#
##################################################
local $docbook2man::docinfo_map = {
'elem:artheader' => \&tr_docinfo,
'elem:bookinfo' => \&tr_docinfo,
'elem:docinfo' => \&tr_docinfo,
'elem:refsect1info' => \&tr_docinfo,
'elem:refsect2info' => \&tr_docinfo,
'elem:refsect3info' => \&tr_docinfo,
'elem:refsynopsisdivinfo' => \&tr_docinfo,
'elem:sect1info' => \&tr_docinfo,
'elem:sect2info' => \&tr_docinfo,
'elem:sect3info' => \&tr_docinfo,
'elem:sect4info' => \&tr_docinfo,
'elem:sect5info' => \&tr_docinfo,
# We use node_title for displaying the section title,
# so we ignore these here.
'elem:title' => sub {},
'elem:titleabbrev' => sub {},
'whitespace' => sub {},
'text' => \&unexpected_node,
'elem' => sub { die NMOC_NEXTSPEC_REDO },
'' => sub {}
};
local $docbook2man::secttoc_map = {
'' => sub { die NMOC_NEXTSPEC_REDO }
};
sub tr_docinfo
{
my $node = shift;
# FIXME More information may need to be printed.!
node_map_ordered_children($node,
{
# Treat bookinfo-bookbiblio the same.
'elem:bookbiblio' => sub { &node_map_ordered_children },
'elem:abstract' => sub {
tr_block_container_with_title(shift, 'Abstract'); },
'elem:legalnotice' => \&tr_block_container,
'' => sub {}
});
}
##################################################
#
# Other utility functions
#
##################################################
# Cache results
$docbook2man::id = undef;
sub getElementByID
{
if(!defined $docbook2man::id) {
$docbook2man::id = getElementsByID($docbook2man::dom);
}
return $docbook2man::id->{(shift)}
}
# Returns title (as element) of given node.
#
sub node_title
{
my $node = shift;
my $use_abbrev = shift;
my $title; # Current title
my $title_map = {
'elem:title' => sub { return shift; },
'elem:titleabbrev' => sub { return shift if $use_abbrev; },
# Particularly annoying because article and book may not have a
# title.
'elem:artheader' => sub {
my ($node, $map) = @_;
my $title;
foreach($node->getChildNodes) {
my $x = node_map($_, $map);
$title = $x if defined $x;
}
return $title;
},
'elem:bookinfo' => sub {
my ($node, $map) = @_;
my $title;
foreach($node->getChildNodes) {
my $x = node_map($_, $map);
$title = $x if defined $x;
}
return $title;
},
'elem:bookbiblio' => sub {
my ($node, $map) = @_;
my $title;
foreach($node->getChildNodes) {
my $x = node_map($_, $map);
$title = $x if defined $x;
}
return $title;
},
'elem:refmeta' => sub {
my ($node, $map) = @_;
my $title;
foreach($node->getChildNodes) {
my $x = node_map($_, $map);
$title = $x if defined $x;
}
return $title;
},
'elem:refentrytitle' => sub { return shift; },
'elem:refnamediv' => sub {
# Don't override real title.
# FIXME This should be customizable.
return if defined $title;
my $title = $docbook2man::dom->createElement('title');
# Put all RefNames together.
foreach($node->getChildNodes) {
if($_->getNodeType == ELEMENT_NODE and
$_->getTagName() eq 'refname')
{
if($title->getChildNodes() > 0) {
$title->addText(', ');
}
foreach($_->cloneNode('deep')->getChildNodes()) {
$title->appendChild($_);
}
}
}
$title->normalize();
return $title;
},
'' => sub {}
};
foreach($node->getChildNodes()) {
my $x = node_map($_, $title_map);
$title = $x if defined $x;
}
return $title;
}
##################################################
#
# Start conversion.
#
##################################################
$docbook2man::dom;
# Separate above into own module, and this as 'front end'.
sub convert
{
$docbook2man::dom = shift;
tr_section($docbook2man::dom->getDocumentElement());
}
$docbook2man::default_section = '1';
$docbook2man::default_misc = '';
$docbook2man::default_date = '';
$docbook2man::default_manual = '';
#use Getopt::Long;
#GetOptions( 'default-section=s' => \$docbook2man::default_section,
# 'default-misc=s' => \$docbook2man::default_misc,
# 'default-date=s' => \$docbook2man::default_date,
# 'default-manual=s' => \$docbook2man::default_manual);
unshift(@ARGV, '-') unless @ARGV;
while(defined(my $f = shift @ARGV)) {
my $parser = new XML::DOM::Parser;
my $doc = $parser->parsefile($f);
convert($doc);
}
syntax highlighted by Code2HTML, v. 0.9.1