LDP/LDP/texi2db/texi2db

1631 lines
38 KiB
Plaintext
Raw Normal View History

#!/usr/bin/perl
#
# this utility converts a texinfo file into LDP WikiText format.
#
use File::Basename;
use FileHandle;
use HTML::Entities;
$version = "0.1";
$errors = 0;
$error = 0;
# runtime options
#
$maxerrors = 10;
$verbose = 0;
$outputtype = "HTML";
# files
#
$requestedfile = "";
$outputfile = "";
$outfh = new FileHandle;
$logfile = "";
$logfh = new FileHandle;
# the following are flags to maintain state
#
# docbook structure flags
#
$inabstract = 1;
$inappendix = 0;
$insect1 = 0;
$insect2 = 0;
$insect3 = 0;
$insect44 = 0;
$intable = 0;
$inrow = 0;
$incol = 0;
$infirstcol = 0;
$tableformat = "";
$inorderedlist = 0;
$initemizedlist = 0;
$inlistitem = 0;
$informalpara = 0;
$inpara = 0;
# these maintain internal program state
#
$line = "";
$originalline = "";
$currentfile = "";
$currentline = "";
$saveline = "";
$badbracketlines = 0;
$badbracketstartline = 0;
$seekend = "";
$literalend = "";
$literal = "";
# here, we hold onto meta-data that we will need to build
# the article or book header structures.
#
$sgmlfile = "";
$title = "";
$authorname = "";
$buf = "";
# these are used when parsing the line for commands
#
$command;
$tag;
$tagplain;
$contents;
# these hold information about the node we are currently in, if any
#
$nodename = "";
$nodenext = "";
$nodeprev = "";
$nodeup = "";
$nodeid = "";
$nodeinit = 0;
# remember this hierarchical information for every node
# we use it if the node doesn't specify a level.
#
%nodenames = ();
%nodeanchors = ();
%nodenexts = ();
%nodeprevs = ();
%nodeups = ();
%nodelevels = ();
# holds user variables
#
%setvalues = ();
%ifsets = ();
# this is used to build a list of index entries to be written when requested
#
%indexes = ();
# These are used for keeping state on the most recent search of any of the following hashes
#
$pattern = "";
$action = "";
$replacement = "";
# In the following hash, the perl expressions are used to search
# each line of the input file. If matched, the Action field defines
# what to do with it.
#
# Note that @-Commands of this type must be at the beginning of the line.
#
# TODO: put these in a .conf file so it can be tweaked or customized
#
# Actions:
#
# DROPLINE Drop it on the floor
# DROPBLOCK Drop the whole block on the floor
# META Meta-Data
# BYE Stop processing the file
# INDEX An index entry
# ORDEREDLIST Numbered (enumerated) list
# ORDEREDLISTEND End an enumerated list
# SEEKEND Skip everything until you find the corresponding @end tag
# NODE A Texinfo node
# SECT? One of the sectioning commands
# APPENDIX? One of the appendix commands
# DEFINFOENCLOSE Load a customized highlighting pattern
# SET Set a variable
# CLEAR Clear a variable
# IFSET Test a variable
#
# @-Command Action
%patterns = (
'\input' =>'DROPLINE',
'@alias' =>'ALIAS',
'@author' =>'META',
'@bye' =>'BYE',
'@c' =>'DROPLINE',
'@cindex' =>'INDEX',
'@clear' =>'CLEAR',
'@comment' =>'DROPLINE',
'@contents' =>'DROPLINE',
'@defindex' =>'DROPLINE', # TODO
'@definfoenclose' =>'DEFINFOENCLOSE',
'@dircategory' =>'DROPLINE',
'@direntry' =>'SEEKEND',
'@display' =>'LITERALBLOCK',
'@enumerate' =>'ORDEREDLIST',
'@end enumerate' =>'ORDEREDLISTEND',
'@example' =>'LITERALBLOCK',
'@format' =>'LITERALBLOCK',
'@headings' =>'DROPLINE',
'@html' =>'SEEKEND',
'@end html' =>'DROLINE',
'@ifhtml' =>'SEEKEND',
'@end ifhtml' =>'DROPLINE',
'@ifinfo' =>'SEEKEND',
'@end ifinfo' =>'DROPLINE',
'@ifnothtml' =>'SEEKEND',
'@end ifnothtml' =>'DROPLINE',
'@ifnotinfo' =>'SEEKEND',
'@end ifnotinfo' =>'DROPLINE',
'@ifnottex' =>'SEEKEND',
'@end ifnottex' =>'DROPLINE',
'@ifset' =>'IFSET', # handled like SEEKEND
'@end ifset' =>'DROPLINE', # or ignored, so ignore this too
'@ifclear' =>'IFCLEAR',
'@end ifclear' =>'DROPLINE',
'@iftex' =>'DROPLINE',
'@end iftex' =>'DROPLINE',
'@ignore' =>'SEEKEND',
'@itemize' =>'ITEMIZEDLIST',
'@end itemize' =>'ITEMIZEDLISTEND',
'@item' =>'ITEM',
'@itemx' =>'ITEM',
'@macro' =>'MACRO',
'@menu' =>'SEEKEND', # TODO
'@node' =>'NODE',
'@page' =>'DROPLINE',
'@printindex' =>'DROPLINE', # TODO
'@set' =>'SET',
'@setchapternewpage' =>'DROPLINE',
'@setfilename' =>'META',
'@settitle' =>'META',
'@smallbook' =>'DROPLINE',
'@smallexample' =>'LITERALBLOCK',
'@sp' =>'DROPLINE',
'@table' =>'TABLE',
'@end table' =>'TABLEEND',
'@vtable' =>'TABLE',
'@end vtable' =>'TABLEEND',
'@tex' =>'SEEKEND',
'@titlepage' =>'SEEKEND',
'@top' =>'SECT1', # sectioning commands
'@chapter' =>'SECT1',
'@section' =>'SECT2',
'@subsection' =>'SECT3',
'@subsubsection' =>'SECT4',
'@centerchap' =>'SECT1',
'@unnumbered' =>'SECT1',
'@unnumberedsec' =>'SECT2',
'@unnumberedsubsec' =>'SECT3',
'@unnumberedsubsubsec' =>'SECT4',
'@majorheading' =>'SECT1',
'@chapheading' =>'SECT1',
'@heading' =>'SECT2',
'@subheading' =>'SECT3',
'@subsubheading' =>'SECT4',
'@appendix' =>'APPENDIX',
'@appendixsec' =>'SECT1',
'@appendixsubsec' =>'SECT2',
'@appendixsubsubsec' =>'SECT3',
);
# these are one-for-one string substitutions
#
# most of them are ISO-8879 codes for international characters,
# used by HTML as well as XML, but there are exceptions.
# There is one special case, "@@", which is the escaped form of @.
# We do not handle @@ here, because it has to come after all other
# @-commands have been processed.
#
# It's fine to replace these with nothing if you want them to be
# simply dropped on the floor.
#
# I keep these in general alphabetical order, except that @end tags
# are put right after their opening form.
#
# Note: Only the left side should be escaped!
#
%substitutions = (
'@.' =>'.',
'@!' =>'!',
'@*' =>'',
'@s' =>' ',
'@"A' =>'Ä',
'@"E' =>'Ë',
'@"I' =>'Ï',
'@"O' =>'Ö',
'@"U' =>'Ü',
'@"a' =>'ä',
'@"e' =>'ë',
'@"i' =>'ï',
'@"o' =>'ö',
'@"u' =>'ü',
'@' . "'" . 'A' =>'Á',
'@' . "'" . 'E' =>'É',
'@' . "'" . 'I' =>'Í',
'@' . "'" . 'O' =>'Ó',
'@' . "'" . 'U' =>'Ú',
'@' . "'" . 'a' =>'á',
'@' . "'" . 'e' =>'é',
'@' . "'" . 'i' =>'í',
'@' . "'" . 'o' =>'ó',
'@' . "'" . 'u' =>'ú',
'@,{c}' =>'¸',
'@-' =>'¯',
'@\.' =>'.',
'@:' =>'',
'@=' =>'',
'@\?' =>'?',
'@^A' =>'Â',
'@^E' =>'Ê',
'@^I' =>'Î',
'@^O' =>'Ô',
'@^U' =>'Û',
'@^a' =>'â',
'@^e' =>'ê',
'@^i' =>'î',
'@^o' =>'ô',
'@^u' =>'û',
'@`A' =>'À',
'@`E' =>'È',
'@`I' =>'Ì',
'@`O' =>'Ò',
'@`U' =>'Ù',
'@`a' =>'à',
'@`e' =>'è',
'@`i' =>'ì',
'@`o' =>'ò',
'@`u' =>'ù',
# '@\{' =>'{', # do these manually at the end so brackets match
# '@\}' =>'}',
'@~A' =>'Ã',
'@~E' =>'&Etilde;',
'@~I' =>'Ĩ',
'@~O' =>'Õ',
'@~U' =>'Ũ',
'@~a' =>'ã',
'@~e' =>'&etilde;',
'@~i' =>'ĩ',
'@~o' =>'õ',
'@~u' =>'ũ',
'@center' =>'',
'@equiv' =>'≡',
'@group' =>'',
'@end group' =>'',
'@exclamdown' =>'¡',
'@noindent' =>'',
'@refill' =>'',
);
# these are inline tags that require some kind of programmatic control
# because they do strange things.
#
%specsubstitutions = (
'@anchor' =>'ANCHOR',
'@ref' =>'REF',
'@uref' =>'UREF',
'@xref' =>'XREF',
'@pxref' =>'PXREF',
'@bullet' =>'*',
'@kbd' =>'KBD',
'@AA' =>'Å',
'@aa' =>'å',
'@AE' =>'Æ',
'@ae' =>'æ',
'@copyright' =>'©',
'@dots' =>'…',
'@minus' =>'-',
'@O' =>'Ø',
'@o' =>'ø',
'@result' =>'=>',
'@TeX' =>'TeX',
'@enddots' =>'…',
);
# These are special block wrapping tags. When we hit one of these,
# we simply wrap whatever comes between the (always paired) tags
# with a set of DocBook tags.
#
# Their handling is subtly different than the %tags.
# We don't just replace them with the DocBook tags, because we
# want any <para> tags to go inside them, not outside.
#
%blocks = (
'\@quotation\b' =>'<blockquote>',
'\@end quotation' =>'</blockquote>',
);
# these are inline @-Commands which are replaced by a set of tags instead of one-for-one
# character substitution. Replacement is inline.
#
# there is also a special case, the "blank" tag, which does what you'd think.
# It removes the existing tag, but doesn't add the wrapper.
#
# Example: @code{foo} becomes <literal>foo</literal>.
#
%tags = (
'@acronym' =>'abbrev',
'@b' =>"emphasis role='bold'",
'@cite' =>'citetitle',
'@code' =>'literal',
'@command' =>'command',
'@dfn' =>"emphasis role='bold'",
'@key' =>'keycap',
'@email' =>'email',
'@emph' =>'emphasis',
'@env' =>'envvar',
'@footnote' =>'footnote',
'@file' =>'filename',
'@i' =>'emphasis',
'@samp' =>'literal',
'@sc' =>'',
'@t' =>'programlisting',
'@var' =>'literal',
'@w' =>'',
);
# this is where definfoenclose definitions go, and they are processed last
#
%definfos = ();
# and this is where macros are stored
#
$inmacro = 0;
$macro = "";
%macros = {};
$macroargs = "";
%macroargs = ();
$macrotext = "";
%macrotext = ();
%aliases = ();
##############################################################################
##############################################################################
##############################################################################
while (1) {
if ($ARGV[0] eq '') {
last;
} elsif ($ARGV[0] eq '--version') {
&version;
exit(0);
} elsif (($ARGV[0] eq '-i') or ($ARGV[0] eq '--include')) {
shift(@ARGV);
if (($ARGV[0] eq 'HTML') or ($ARGV[0] eq 'TEX') or ($ARGV[0] eq 'INFO')) {
$outputtype = $ARGV[0];
} else {
&raiseerror("invalid include format: $ARGV[0]");
exit(1);
}
} elsif (($ARGV[0] eq '-f') or ($ARGV[0] eq '--file')) {
shift(@ARGV);
$requestedfile = $ARGV[0];
} elsif (($ARGV[0] eq '-o') or ($ARGV[0] eq '--output-to')) {
shift(@ARGV);
$outputfile = $ARGV[0];
} elsif (($ARGV[0] eq '-l') or ($ARGV[0] eq '--log-to')) {
shift(@ARGV);
$logfile = $ARGV[0];
} elsif (($ARGV[0] eq '-e') or ($ARGV[0] eq '--max-errors')) {
shift(@ARGV);
$maxerrors = $ARGV[0];
} elsif (($ARGV[0] eq '-v') or ($ARGV[0] eq '--verbose')) {
$verbose++;
} elsif (($ARGV[0] eq '-h') or ($ARGV[0] eq '--help')) {
&usage;
} else {
&raiseerror("unrecognized option: $ARGV[0]\n");
$error = 1;
&usage;
}
shift(@ARGV);
}
# be sure to open the log file before trying to write any messages to it!
#
if ($logfile) {
open ($logfh, "> $logfile") or die "cannot write to log file $logfile.\n";
}
$verbose = 3 if ($verbose > 3);
&message("verbose mode on.") if ($verbose == 1);
&message("debugging mode on.") if ($verbose ==2);
&message("insanity mode on.") if ($verbose ==3);
if ($outputtype eq 'HTML') {
$patterns{'@ifhtml'} = 'DROPLINE';
$patterns{'@end ifhtml'} = 'DROPLINE';
$patterns{'@ifinfo'} = 'SEEKEND';
$patterns{'@end ifinfo'} = 'DROPLINE';
$patterns{'@iftex'} = 'SEEKEND';
$patterns{'@end iftex'} = 'DROPLINE';
$patterns{'@ifnothtml'} = 'SEEKEND';
$patterns{'@end ifnothtml'} = 'DROPLINE';
$patterns{'@ifnotinfo'} = 'DROPLINE';
$patterns{'@end ifnotinfo'} = 'DROPLINE';
$patterns{'@ifnottex'} = 'DROPLINE';
$patterns{'@end ifnottex'} = 'DROPLINE';
&message("including HTML text") if ($verbose);
} elsif ($outputtype eq 'INFO') {
$patterns{'@ifhtml'} = 'SEEKEND';
$patterns{'@end ifhtml'} = 'DROPLINE';
$patterns{'@ifinfo'} = 'DROPLINE';
$patterns{'@end ifinfo'} = 'DROPLINE';
$patterns{'@iftex'} = 'SEEKEND';
$patterns{'@end iftex'} = 'DROPLINE';
$patterns{'@ifnothtml'} = 'DROPLINE';
$patterns{'@end ifnothtml'} = 'DROPLINE';
$patterns{'@ifnotinfo'} = 'SEEKEND';
$patterns{'@end ifnotinfo'} = 'DROPLINE';
$patterns{'@ifnottex'} = 'DROPLINE';
$patterns{'@end ifnottex'} = 'DROPLINE';
&message("including INFO text") if ($verbose);
} elsif ($outputtype eq 'TEX') {
$patterns{'@ifhtml'} = 'SEEKEND';
$patterns{'@end ifhtml'} = 'DROPLINE';
$patterns{'@ifinfo'} = 'SEEKEND';
$patterns{'@end ifinfo'} = 'DROPLINE';
$patterns{'@iftex'} = 'DROPLINE';
$patterns{'@end iftex'} = 'DROPLINE';
$patterns{'@ifnothtml'} = 'DROPLINE';
$patterns{'@end ifnothtml'} = 'DROPLINE';
$patterns{'@ifnotinfo'} = 'DROPLINE';
$patterns{'@end ifnotinfo'} = 'DROPLINE';
$patterns{'@ifnottex'} = 'SEEKEND';
$patterns{'@end ifnottex'} = 'DROPLINE';
&message("including TEX text") if ($verbose);
}
if ($outputfile) {
&message("output will go to $outputfile") if ($verbose);
open($outfh, "> $outputfile");
}
while (<DATA>) {
$template .= $_;
}
&processfile ("$requestedfile");
&writefile;
close($outfh) if ($outputfile);
close($logfh) if ($logfile);
sub processfile {
my($filename,
$basename,
$path,
$ext,
$includefile,
$linenumber,
);
$macrolinecount = 0;
my $fh = new FileHandle;
$filename = @_[0];
$filename = 'STDIN' unless ($filename);
($basename, $path, $ext) = fileparse($filename);
&message("processing $filename") if ($verbose);
$linenumber = 0;
open $fh, "<$filename" or raiseerror("cannot open $filename\n");
LINE: while ($line = <$fh>) {
chomp($line);
$linenumber++;
$currentfile = $filename;
$currentline = $linenumber;
&cleanline;
$originalline = $line;
$trimline;
if ($seekend) {
$seekend = '' if ($line =~ /$seekend/);
next LINE;
}
&message("LINE $currentline: $line") if ($verbose > 1);
$line =~ s/&/&amp;/g; # keep before the &lt; &gt; so we don't clobber them
$line =~ s/\</&lt;/g;
$line =~ s/\>/&gt;/g;
$line =~ s/\@\@\}/DCM_AT\}/g;
$line =~ s/\@\{/DCM_LB/g;
$line =~ s/\@\}/DCM_RB/g;
# keep reading until we have only complete tags
#
if ($saveline) {
$line = $saveline . ' ' . $line;
$saveline = '';
&message("line restored, line: $line") if ($verbose > 1);
}
unless (&bracketsmatch($line)) {
&message("bracket mismatch") if ($verbose > 2);
$saveline = $line;
next LINE;
}
if ($line =~ /^\@include/) {
$includefile = $line;
$includefile =~ s/^\@include\s+//;
$includefile = $path . $includefile;
$line = '';
if (-e $includefile) {
&message("including $includefile") if ($verbose);
&processfile ("$includefile");
} else {
&raiseerror("include file $includefile not found.");
}
} elsif ($literalend) {
if ($line =~ /$literalend/) {
$literalend = '';
&closeliteral;
} else {
&writeline;
}
} elsif ($inmacro) {
($pattern, $action) = &matchpattern();
if ($action eq 'SEEKEND') {
$seekend = $pattern;
$seekend =~ s/\@/\@end /;
next LINE;
} elsif ($action eq 'DROPLINE') {
next LINE;
}
if ($line =~ /\@end\s+macro/) {
#save macro
$macroargs{$macro} = $macroargs;
$macrotext{$macro} = $macrotext;
&message("macro name: $macro args: $macroargs text: $macrotext") if ($verbose > 2);
$inmacro = 0;
$macroargs = '';
$macrotext = '';
$macrolinecount = 0;
} elsif ($line =~ /\@quote-arg/) {
} else {
# in info output, {foo} is used to indicate a link.
# This doesn't appear in the source, but can appear
# in macros, so replace them with cross references.
#
$line =~ s/DCM_LB(.*?)DCM_RB/\@ref\{$1\}/g;
&message("macro line: $line") if ($verbose > 2);
$macrotext .= $line;
$macrolinecount++;
if ($macrolinecount >= 10) {
exit(1);
}
}
} else {
($pattern, $action) = &matchblock();
if ($action) {
&message("matched block $pattern") if ($verbose > 2);
&closepara;
$buf .= $action;
next LINE;
}
($pattern, $action) = &matchpattern();
if ($action eq 'DROPLINE') {
next LINE;
} elsif ($action eq 'SEEKEND') {
$seekend = $pattern;
$seekend =~ s/\@/\@end /;
} elsif ($action eq 'LITERALBLOCK') {
$literalend = $pattern;
$literalend =~ s/\@/\@end /;
&literal;
} elsif ($action eq 'ALIAS') {
&alias;
} elsif ($action eq 'MACRO') {
&macro;
} elsif ($action eq 'META') {
&meta;
} elsif ($action eq 'NODE') {
&node;
} elsif ($action eq 'SECT1') {
&sect1;
} elsif ($action eq 'SECT2') {
&sect2;
} elsif ($action eq 'SECT3') {
&sect3;
} elsif ($action eq 'SECT4') {
&sect4;
} elsif ($action eq 'APPENDIX') {
&appendix;
} elsif ($action eq 'PARA') {
&closeformalpara;
&para;
} elsif ($action eq 'ORDEREDLIST') {
&orderedlist;
} elsif ($action eq 'ORDEREDLISTEND') {
&closeorderedlist;
} elsif ($action eq 'ITEMIZEDLIST') {
&itemizedlist;
} elsif ($action eq 'ITEMIZEDLISTEND') {
&closeitemizedlist;
} elsif ($action eq 'ITEM') {
&item;
} elsif ($action eq 'TABLE') {
$tableformat = $line;
$tableformat =~ s/^\@\w+\s+//;
&message("table format: $tableformat") if ($verbose > 2);
&table;
$infirstcol = 0;
} elsif ($action eq 'TABLEEND') {
&closetable;
} elsif ($action eq 'INDEX') {
&message("indexing not yet supported") if ($verbose);
} elsif ($action eq 'DEFINFOENCLOSE') {
&definfoenclose;
} elsif ($action eq 'SET') {
&set;
} elsif ($action eq 'CLEAR') {
&clear;
} elsif ($action eq 'IFSET') {
$seekend = '\@end ifset' unless (&ifset());
} elsif ($action eq 'IFCLEAR') {
$seekend = '\@end ifclear' if (&ifset());
} elsif ($action eq 'BYE') {
&closesect1;
last;
} elsif ($action eq 'ERROR') {
&raiseerror("Hit ERROR tag with $pattern on $line");
} elsif ($action eq '') {
if ($intable) {
&message("table: $line") if ($verbose > 2);
($pattern, $action) = &matchpattern();
if ($action eq 'TABLEEND') {
&closetable;
$infirstcol = 0;
} else {
if ($infirstcol) {
&closetablecol;
&tablecol;
$infirstcol = 0;
}
&writeline;
}
} else {
&writeline;
}
} else {
&raiseerror("Unknown action: $action on command $pattern");
}
}
}
close($fh);
}
sub cleanline {
$line =~ s/\x0c//;
$line =~ s/\xd7//;
}
sub writefile {
&closeappendix;
print $outfh $buf;
print $outfh '</article>'. "\n";
}
####################
# PATTERN MATCHING #
####################
# this is for beginning-of-line @-commands
#
sub matchpattern {
$pattern = "";
foreach $key (keys %patterns) {
$pattern = quotemeta($key);
if ($line =~ /^$pattern\b/) {
return ($pattern, $patterns{$key});
}
}
return ('', '');
}
# this is for special wrapped blocks, like blockquotes
#
sub matchblock {
foreach $key (keys %blocks) {
if ($line =~ /$key/) {
return ($key, $blocks{$key});
}
}
}
# the rest are all inline processing
#
sub convertinline {
# Do this first to avoid @@foo from being
# identified as a command later.
$line =~ s/\@\@/DCM_AT/;
TAG: while ($line =~ /\@\w+\{[^\{]*?\}/) {
$command = $line;
$command =~ s/.*(\@\w+\{[^{]*?\}).*/\1/;
$tag = $command;
$tag =~ s/(.*)\{.*/\1/;
$tagplain = $tag;
$tagplain =~ s/\@//;
$contents = $command;
$contents =~ s/.*\{(.*)\}/\1/;
&message("line: $line") if ($verbose > 1);
&message("command: $command") if ($verbose > 2);
&message("tag: $tag") if ($verbose > 2);
&message("tagplain: $tagplain") if ($verbose > 2);
&message("contents: $contents") if ($verbose > 2);
# substitutions (only @{} type will get caught here)
#
if (exists $substitutions{$tag}) {
&message("matched character substitution $tag") if ($verbose >2);
$replacement = $substitutions{$tag};
&replaceinline;
next TAG;
}
# macros
#
if (exists $macros{$tagplain}) {
&message("matched macro $tagplain") if ($verbose >2);
$macro = $macros{$tagplain};
$macroargs = "\\\\" . $macroargs{$tagplain} . "\\\\";
$macrotext = $macrotext{$tagplain};
$macroarg = $command;
$macroarg =~ s/^.*?\@$macro\{//;
$macroarg =~ s/\}.*?$//;
$macrotext =~ s/\@$macro\{([^\{]*?)\}/$macroarg/;
$macrotext =~ s/$macroargs/$macroarg/g;
$replacement = $macrotext;
&replaceinline;
next TAG;
}
# special character substitutions
#
if (exists $specsubstitutions{$tag}) {
&message("matched special substitution $tag") if ($verbose >2);
$replacement = $specsubstitutions{$tag};
&replacespecsubst;
&replaceinline;
next TAG;
}
# docbook wrapper tags
#
if (exists $tags{$tag}) {
&message("matched docbook wrapper tag $tag") if ($verbose >2);
$replacement = $tags{$tag};
$replacement = '' if (($replacement eq 'programlisting') and ($literalend));
if ($replacement) {
($tag, $attributes) = split(/ /, $replacement);
if ($attributes) {
$replacement = "\<$tag $attributes\>$contents\<\/$tag\>";
} else {
$replacement = "\<$tag\>$contents\<\/$tag\>";
}
} else {
$replacement = $contents;
}
&replaceinline;
next TAG;
}
if (exists $definfos{$tagplain}){
&message("matched definfo $tagplain") if ($verbose >2);
$action = $definfos{$tagplain};
($prefix, $suffix) = split(/,/,$action);
$replacement = $prefix . $contents . $suffix;
&message("definfo prefix: $prefix, suffix: $suffix, contents: $contents") if ($verbose > 1);
&replaceinline;
next TAG;
}
if (exists $setvalues{$contents}) {
&message("matched set value $contents") if ($verbose > 2);
$replacement = $setvalues{$contents};
&replaceinline;
next TAG;
}
if (exists $aliases{$tag}) {
&message("matched alias $tag") if ($verbose >2);
$replacement = $aliases{$tag} . '{' . $contents . '}';
&replaceinline;
next TAG;
}
&raiseerror("cannot resolve $command");
$replacement = "ERROR";
&replaceinline;
}
# fix character-level substitutions.
#
for $key (keys %substitutions) {
$key = quotemeta($key);
$line =~ s/$key/$substitutions{$key}/g
}
$line =~ s/DCM_AT/\@/gi;
}
sub replaceinline {
&message("replacing $command with $replacement") if ($verbose > 1);
$command = quotemeta($command);
$line =~ s/$command/$replacement/;
}
sub replacespecsubst {
my $anchor;
my $link;
my $linkname;
my $keystring;
my @keycombos;
my @keycaps;
my $mykeys;
if ($replacement eq 'REF') {
&message("making xref on line $line") if ($verbose > 1);
$link = $contents;
$linktitle = &trim($link);
$link = &anchorfix($link);
&message("link: $link, title; $linktitle") if ($verbose > 2);
&raiseerror("NO ANCHOR in $line") unless ($link);
$replacement = "\<link linkend='$link'\>$linktitle\<\/link\>";
&message("made xref to tag $link on line: $line") if ($verbose > 1);
} elsif ($replacement eq 'PXREF') {
$link = $contents;
($link, $linkname) = split(/,/, $link);
$link = &trim($link);
$linkname = &trim($linkname);
$linkname = $link unless ($linkname);
$replacement = "see \<ulink url='$link'\>$linkname\<\/ulink\>";
&message("ulink: $link, linkname: $linkname, line: $line") if ($verbose > 2);
} elsif ($replacement eq 'UREF') {
$link = $contents;
($link, $linkname) = split(/,/, $link);
$link = &trim($link);
$linkname = &trim($linkname);
$linkname = $link unless ($linkname);
$replacement = "\<ulink url='$link'\>$linkname\<\/ulink\>";
&message("ulink: $link, linkname: $linkname, line: $line") if ($verbose > 2);
} elsif ($replacement eq 'XREF') {
($link, $linkname, $foo, $foo) = split(/,/, $contents);
$link = &anchorfix(&trim($link));
$linkname = &trim($linkname);
$linkname = $link unless ($linkname);
&message("xref, contents: $contents, link: $link, linkname: $linkname") if ($verbose > 1);
if ($nodelevels{$link}) {
$replacement = "\<xref linkend='$link' endterm='$link-title'\/\>";
} else {
$replacement = "\<link linkend='$link'\>$linkname\<\/link\>";
}
} elsif ($replacement eq 'ANCHOR') {
$link = $contents;
$link = &anchorfix($link);
$replacement = "<anchor id='$link'\/\>";
} elsif ($replacement eq 'KBD') {
$keystring = $contents;
# I found at least one case where <keycap> is already here:
# gTop<keycap>RET</keycap>, in the info manual.
# If we have those, throw away the keycap tags and hyphenate,
# so we can keycombo properly.
#
$keystring =~ s/\<keycap\>/-/g;
$keystring =~ s/\<\/keycap\>/-/g;
$keystring =~ s/^-+//g;
$keystring =~ s/-+$//g;
$keystring =~ s/-+\s+/-/g;
$keystring =~ s/\s+-+/-/g;
@keycombos = split(/\s+/, $keystring);
foreach $keycombo (@keycombos) {
&message("keycombo: $keycombo") if ($verbose > 2);
@keycaps = split(/-/, $keycombo);
if (scalar @keycaps > 1) {
&message("making keycombo") if ($verbose > 2);
$mykeys .= "\<keycombo action='simul'\>";
foreach $keycap (@keycaps) {
$mykeys .= "\<keycap\>$keycap\<\/keycap\>";
}
$mykeys .= '</keycombo>'
} else {
$mykeys .= "\<keycap\>$keycaps[0]\<\/keycap\>";
}
}
$replacement = $mykeys;
} else {
$replacement = $specsubstitutions{$tag};
}
}
sub anchorfix {
my $anchor = $_[0];
$anchor = lc(&trim($anchor));
$anchor = decode_entities($anchor);
$anchor =~ s/-/-dash-/g;
$anchor =~ s/&/-and-/g;
$anchor =~ s/;//g;
$anchor = encode_entities($anchor);
$anchor =~ s/&(\w)grave/\1/g;
$anchor =~ s/&(\w)acute/\1/g;
$anchor =~ s/&(\w)circ/\1/g;
$anchor =~ s/&(\w)uml/\1/g;
$anchor =~ s/&(\w)tilde/\1/g;
$anchor =~ s/&(\w)cedil/\1/g;
$anchor =~ s/&/-and-/g;
$anchor =~ s/;//g;
$anchor =~ s/\//-slash-/g;
$anchor =~ s/\s+/-/g;
$anchor =~ s/'//g;
$anchor =~ s/,/-comma-/g;
$anchor =~ s/\./-dot-/g;
$anchor =~ s/!/-bang-/g;
$anchor =~ s/\?/-question-/g;
$anchor =~ s/\+/-plus-/g;
$anchor =~ s/\*/-x-/g;
$anchor =~ s/\(/-op-/g;
$anchor =~ s/\)/-cp-/g;
$anchor =~ s/\@/-at-/g;
$anchor =~ s/dcm_at/-at-/gi;
$anchor =~ s/\^/-hat-/g;
$anchor =~ s/=/-eq-/g;
$anchor =~ s/\$/S/;
$anchor =~ s/~/-tilde-/g;
$anchor =~ s/0/-zero-/g;
$anchor =~ s/1/-one-/g;
$anchor =~ s/2/-two-/g;
$anchor =~ s/3/-three-/g;
$anchor =~ s/4/-four-/g;
$anchor =~ s/5/-five-/g;
$anchor =~ s/6/-six-/g;
$anchor =~ s/7/-seven-/g;
$anchor =~ s/8/-eight-/g;
$anchor =~ s/9/-nine-/g;
$anchor =~ s/\|/-pipe-/g;
$anchor =~ s/\[/-lsqb-/g;
$anchor =~ s/\]/-rsqb-/g;
$anchor =~ s/^-+//;
$anchor =~ s/-+$//;
$anchor =~ s/--/-/g; # get rid of double, initial and trailing hyphens
return &trim($anchor);
}
#############
# META-DATA #
#############
# this routine processes meta-data @-Commands
#
sub meta {
return if (&metaarg('\@setfilename', $sgmlfile));
return if (&metaarg('\@settitle', $title));
return if (&metaarg('\@author', $authorname));
&raiseerror("Unknown meta-data command $line");
}
# Meta-data commands have arguments,
# so if the pattern matches, store the argument
# to the variable in $_[1]
#
sub metaarg {
my $pattern = $_[0];
if ($line =~ /$pattern/) {
$_[1] = $line;
$_[1] =~ s/$pattern//;
}
}
################
# WRITE OUTPUT #
################
sub writeline {
my $temp = &trim($line);
&convertinline;
unless (&bracketsmatch($line)) {
&raiseerror("brackets do not match in line: $line");
last;
}
unless ($literal) {
if ($temp eq '') {
&closeformalpara;
} else {
unless (($inpara) or ($intable)) {
if (($nodename) and ($nodeinit == 0)) {
&initnode;
}
&para;
}
}
}
$line =~ s/\@\@/\@/g;
$line =~ s/DCM_AT/\@/gi;
$line =~ s/DCM_LB/\{/gi;
$line =~ s/DCM_RB/\}/gi;
$buf .= $line . "\n";
if (($linenumber % 1000 == 0) and ($inabstract == 0)) {
print $outfh $buf;
$buf = '';
}
}
##############################################
# STRUCTURAL TEXINFO -> DOCBOOK TRANSLATIONS #
##############################################
# this is for when the source never set a position for this node in the
# chapter-style hierarchy, which we use.
#
# We have to make a best guess based on the node's settings,
# so put it right under whoever it calls the "up" node.
#
sub initnode {
my $parentname = $nodeup;
my $parentlevel = $nodelevels{$parentname};
if ($parentlevel == "A") {
&sect1;
} elsif ($parentlevel == 1) {
&sect2;
} elsif ($parentlevel == 2) {
&sect3
} elsif ($parentlevel == 3) {
&sect4;
} else {
&raiseerror("The parent node, $parentname, could not be found.");
}
}
sub node {
$line =~ s/\@node\s+?//;
&convertinline;
($nodename, $nodenext, $nodeprev, $nodeup) = split(/,/, $line);
$nodename = &trim($nodename);
$nodeid = &anchorfix($nodename);
$nodeprev = &trim($nodeprev);
$nodenext = &trim($nodenext);
$nodeup = &trim($nodeup);
$nodeinit = 0;
&message("Name: $nodename Next: $nodenext Previous: $nodeprev Up: $nodeup") if ($verbose > 1);
&closeformalpara;
if ($inabstract) {
$inabstract = 0;
&message("copying meta-data to docbook") if ($verbose);
$template =~ s/%%TITLE/$title/;
if ($buf =~ //) {
$template =~ s/%%ABSTRACT/$buf/;
} else {
$template =~ s/\<abstract\>%%ABSTRACT\<\/abstract\>//s;
}
print $outfh $template . "\n";
$buf = "";
}
$nodenames{$nodename} = $nodename;
$nodenexts{$nodename} = $nodenext;
$nodeprevs{$nodename} = $nodeprev;
$nodeups{$nodename} = $nodeup;
}
# Avoid having two nodes with the same title.
# The first one wins.
#
sub avoiddupnode {
if (exists $nodeanchors{uc($nodename)}) {
&message("discarding duplicate anchor: $nodeid") if ($verbose > 2);
$nodeid = 0;
} else {
&message("storing anchor: $nodeid") if ($verbose > 2);
$nodeanchors{uc($nodename)} = $nodeid;
}
}
sub appendix {
&closeappendix;
&avoiddupnode;
if ($nodeid) {
$buf .= "\<appendix id='$nodeid'\>\<title id='$nodeid-title'\>$nodename\<\/title\>\n";
} else {
$buf .= "\<appendix\>\<title\>$nodename\<\/title\>\n";
}
$inappendix = 1;
$nodeinit = 1;
$nodelevels{$nodename} = "A";
&message("node: $nodename level to $nodelevels{$nodename}") if ($verbose > 0);
}
sub sect1 {
&closesect1;
&avoiddupnode;
if ($nodeid) {
$buf .= "\<sect1 id='$nodeid'\>\<title id='$nodeid-title'\>$nodename\<\/title\>\n";
} else {
$buf .= "\<sect1\>\<title\>$nodename\<\/title\>\n";
}
$insect1 = 1;
$nodeinit = 1;
$nodelevels{$nodename} = 1;
&message("node: $nodename level to $nodelevels{$nodename}") if ($verbose > 0);
}
sub sect2 {
&closesect2;
&avoiddupnode;
if ($inappendix) {
if ($nodeid) {
$buf .= "\<sect1 id='$nodeid'\>\<title id='$nodeid-title'\>$nodename\<\/title\>\n";
} else {
$buf .= "\<sect1\>\<title\>$nodename\<\/title\>\n";
}
$nodelevels{$nodename} = 1;
} else {
if ($nodeid) {
$buf .= "\<sect2 id='$nodeid'\>\<title id='$nodeid-title'\>$nodename\<\/title\>\n";
} else {
$buf .= "\<sect2\>\<title\>$nodename\<\/title\>\n";
}
$nodelevels{$nodename} = 2;
}
$insect2 = 1;
$nodeinit = 1;
$nodelevels{$nodename} = 2;
&message("node: $nodename level to $nodelevels{$nodename}") if ($verbose > 0);
}
sub sect3 {
&closesect3;
&avoiddupnode;
if ($inappendix) {
if ($nodeid) {
$buf .= "\<sect2 id='$nodeid'\>\<title id='$nodeid-title'\>$nodename\<\/title\>\n";
} else {
$buf .= "\<sect2\>\<title\>$nodename\<\/title\>\n";
}
$nodelevels{$nodename} = 2;
} else {
if ($nodeid) {
$buf .= "\<sect3 id='$nodeid'\>\<title id='$nodeid-title'\>$nodename\<\/title\>\n";
} else {
$buf .= "\<sect3\>\<title\>$nodename\<\/title\>\n";
}
$nodelevels{$nodename} = 3;
}
$insect3 = 1;
$nodeinit = 1;
$nodelevels{$nodename} = 3;
&message("node: $nodename level to $nodelevels{$nodename}") if ($verbose > 0);
}
sub sect4 {
&closesect4;
&avoiddupnode;
if ($inappendix) {
if ($nodeid) {
$buf .= "\<sect3 id='$nodeid'\>\<title id='$nodeid-title'\>$nodename\<\/title\>\n";
} else {
$buf .= "\<sect3\>\<title\>$nodename\<\/title\>\n";
}
$nodelevels{$nodename} = 3;
} else {
if ($nodeid) {
$buf .= "\<sect4 id='$nodeid'\>\<title id='$nodeid-title'\>$nodename\<\/title\>\n";
} else {
$buf .= "\<sect4\>\<title\>$nodename\<\/title\>\n";
}
$nodelevels{$nodename} = 4;
}
$insect4 = 1;
$nodeinit = 1;
$nodelevels{$nodename} = 4;
&message("node: $nodename level to $nodelevels{$nodename}");
}
sub table {
&closetable;
$buf .= '<informaltable><tgroup cols=' . "'2'" . '><tbody>' . "\n";
$intable = 1;
}
sub tablerow {
&closetablerow;
$buf .= '<row>';
$inrow = 1;
}
sub tablecol {
&closetablecol;
$buf .= '<entry>';
$incol = 1;
}
sub orderedlist {
&closeformalpara;
$buf .= '<orderedlist>';
$inorderedlist = 1;
}
sub itemizedlist {
&closeformalpara;
$buf .= '<itemizedlist>';
$initemizedlist = 1;
}
sub item {
if ($intable) {
&message("table item: $line") if ($verbose > 2);
unless ($infirstcol) {
&tablerow;
&tablecol;
$infirstcol = 1;
}
$line =~ s/\@item\w*?\s+//;
$line = &trim($line);
$line = $tableformat . "\{$line\}" unless ($tableformat eq '@asis');
writeline;
} else {
&listitem;
}
}
sub listitem {
&closelistitem;
$buf .= '<listitem>';
$inlistitem = 1;
}
sub formalpara {
my $title = $_[0];
my $id = &anchorfix($title);
&closeformalpara;
#$buf .= "\<formalpara id='$id'\>\<title\>$title\<\/title\>\n\<para\>";
$informalpara = 1;
$inpara = 1;
$nodeinit = 1;
}
sub para {
&closeformalpara;
$buf .= '<para>';
$inpara = 1;
}
sub literal {
if ($pattern =~ /\bformat\b/) {
$literal = 'programlisting';
$buf .= '<programlisting>' . "\n";
} elsif ($pattern =~ /\bdisplay\b/) {
$literal = 'screen';
$buf .= '<screen>';
} elsif ($pattern =~ /\bexample\b/) {
$literal = 'programlisting';
$buf .= '<programlisting>';
} elsif ($pattern =~ /\bsmallexample\b/) {
$literal = 'programlisting';
$buf .= '<programlisting>';
}
}
sub closeappendix {
&closesect1;
if ($inappendix) {
$buf .= '</appendix>' . "\n";
$inappendix = 0;
}
}
sub closesect1 {
&closesect2;
if ($insect1) {
$buf .= '</sect1>' . "\n\n";
$insect1 = 0;
}
}
sub closesect2 {
&closesect3;
if ($insect2) {
if ($inappendix) {
$buf .= '</sect1>' . "\n\n";
} else {
$buf .= '</sect2>' . "\n\n";
}
$insect2 = 0;
}
}
sub closesect3 {
&closesect4;
if ($insect3) {
if ($inappendix) {
$buf .= '</sect2>' . "\n\n";
} else {
$buf .= '</sect3>' . "\n\n";
}
$insect3 = 0;
}
}
sub closesect4 {
&closeformalpara;
if ($insect4) {
if ($inappendix) {
$buf .= '</sect3>' . "\n\n";
} else {
$buf .= '</sect4>' . "\n\n";
}
$insect4 = 0;
}
}
sub closetable {
&closetablerow;
if ($intable) {
$buf .= '</tbody></tgroup></informaltable>' . "\n";
$intable = 0;
}
}
sub closetablerow {
&closetablecol;
if ($inrow) {
$buf .= '</row>' . "\n";
$inrow = 0;
}
}
sub closetablecol {
if ($incol) {
$buf .= '</entry>' . "\n";
$incol = 0;
}
}
sub closeorderedlist {
&closelistitem;
if ($inorderedlist) {
$buf .= '</orderedlist>' . "\n";
$inorderedlist = 0;
}
}
sub closeitemizedlist {
&closelistitem;
if ($initemizedlist) {
$buf .= '</itemizedlist>' . "\n";
$initemizedlist = 0;
}
}
sub closelistitem {
&closeformalpara;
if ($inlistitem) {
$buf .= '</listitem>' . "\n";
$inlistitem = 0;
}
}
sub closeformalpara {
&closepara;
if ($informalpara) {
# $buf .= '</formalpara>';
$informalpara = 0;
}
}
sub closepara {
if ($inpara) {
$buf .= '</para>';
$inpara = 0;
}
}
sub closeliteral {
if ($literal) {
$buf .= '</' . $literal . '>' . "\n";
$literal = '';
}
}
################
# CONDITIONALS #
################
sub set {
my ($name,
$value);
($foo, $name, $value) = split(/\s+/, $line);
$value = 1 unless ($value);
$setvalues{$name} = $value;
&message("set $name to $value") if ($verbose > 1);
$value = $setvalues{$name};
&message("read back $value") if ($verbose > 2);
}
sub clear {
my $name;
($foo, $name) = split(/\s+/, $line);
$setvalues{$name} = 0;
&message("cleared $name") if ($verbose > 1);
}
sub ifset {
my $name;
($foo, $name) = split(/\s+/, $line);
return $ifsets{$name};
}
#################
# META-LANGUAGE #
#################
sub alias {
my $alias;
($foo, $alias) = split(/\s/, $line);
($alias, $command) = split(/=/, $alias);
&message("alias: $alias, command: $command") if ($verbose > 2);
$aliases{'@' . $alias} = $command;
}
sub macro {
$line =~ s/\@macro\s+?(.*?)\{(.*?)\}/\1,\2/;
($macro, $macroargs) = split(/,/, $line);
$macro = &trim($macro);
$macros{$macro} = $macro;
$macrotext = '';
$inmacro = 1;
&message("adding macro $macro") if ($verbose > 2);
}
# load custom @-commands
#
sub definfoenclose {
my ($name,
$prefix,
$suffix,
$key,
$replacement);
$line =~ s/\@definfoenclose\s+//;
($name, $prefix, $suffix) = split(/,/, $line);
$replacement = $prefix. ',' . $suffix;
$definfos{$name} = $replacement;
&message("custom definfoenclosure: \[$name\] \[$prefix\] \[$suffix\]") if (verbose > 1);
}
###########
# INDEXES #
###########
sub defindex {
$line =~ s/^\@defindex\s+//;
$indexes{$line} = 1;
}
####################
# SUPPORT ROUTINES #
####################
sub bracketsmatch {
my $bline = ' ' . $_[0] . ' ';
my $left = scalar split(/\{/, $bline) - 1;
my $right = scalar split(/\}/, $bline) - 1;
if ($right == $left) {
$badbracketlines = 0;
$badbracketstartline = $currentline + 1
} else {
$badbracketlines++;
&message("unmatched \{\} (left: $left, right: $right) on line $line") if ($verbose > 1);
if ($badbracketlines >= 20) {
&raiseerror("Mismatched brackets starting on line $badbracketstartline: $line");
}
}
return ($right == $left);
}
sub trimline {
$line = &trim($line);
}
sub trim {
my $temp = $_[0];
$temp =~ s/^\s+//g;
$temp =~ s/\s+$//g;
return $temp;
}
sub raiseerror {
my $errmsg = "ERROR: $currentfile $currentline $_[0]\n";
print STDERR $errmsg;
$errors++;
if (($maxerrors) and ($errors >= $maxerrors)) {
print STDERR "aborting after $errors errors.\n";
&writefile;
exit(1);
}
}
sub message {
my $message;
$message = $_[0];
if ($logfile) {
print $logfh "$message\n";
} else {
print "$message\n";
}
}
sub version {
print "texi2db version $version\n";
print "Copyright (c) 2002 David Merrill \<david\@lupercalia.net\>.\n";
print "\n";
print "Converts a Texinfo file into DocBook.\n";
print "\n";
print "This is free software; see the source for copying conditions. There is no\n";
print "warranty; not even for merchantability or fitness for a particular purpose.\n";
}
sub usage {
&version;
print "\n";
print "usage: texi2db [OPTIONS]... [FILE]\n";
print "-i, --include HTML|INFO|TEX\n";
print " include the specified text.\n";
print "-f, --file FILE read from file rather than STDIN.\n";
print "-o, --output-to FILE write to file rather than STDOUT.\n";
print "-l, --log-to FILE write status messages to file rather than STDOUT.\n";
print "-v, --verbose show diagnostic output.\n";
print " use twice for lots of detail.\n";
print " use thrice for insane amounts of detail.\n";
print "-e, --max-errors MAX show diagnostic output.\n";
print " use '--max-errors 0' to disable.\n";
print "-h, --help show this usage message and exit.\n";
print " --version show the program version and exit.\n";
exit($error);
}
__END__
<?xml version='1.0'?>
<!DOCTYPE article PUBLIC '-//OASIS//DTD DocBook XML V4.1.2//EN'
"http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd" [
<!ENTITY hellip "&#8230;">
<!ENTITY Oslash "&#216;">
]>
<article>
<articleinfo>
<title>%%TITLE</title>
<abstract>%%ABSTRACT</abstract>
</articleinfo>