mirror of
https://git.lyx.org/repos/lyx.git
synced 2024-12-27 06:19:36 +00:00
5a8177491d
git-svn-id: svn://svn.lyx.org/lyx/lyx-devel/trunk@3327 a592a061-630c-0410-9148-cb99ea01b6c8
1521 lines
54 KiB
Perl
1521 lines
54 KiB
Perl
# This file is part of reLyX
|
|
# Copyright (c) 1998-9 Amir Karger karger@post.harvard.edu
|
|
# You are free to use and modify this code under the terms of
|
|
# the GNU General Public Licence version 2 or later.
|
|
|
|
package BasicLyX;
|
|
# This package includes subroutines to translate "clean" LaTeX to LyX.
|
|
# It translates only "basic" stuff, which means it doesn't do class-specific
|
|
# things. (It uses the TeX parser Text::TeX)
|
|
use strict;
|
|
|
|
use RelyxTable; # Handle LaTeX tables
|
|
use RelyxFigure; # Handle LaTeX figures
|
|
use Verbatim; # Copy stuff verbatim
|
|
|
|
use vars qw($bibstyle_insert_string $bibstyle_file $Begin_Inset_Include);
|
|
$bibstyle_insert_string = "%%%%%Insert bibliographystyle file here!";
|
|
$bibstyle_file = "";
|
|
$Begin_Inset_Include = "\\begin_inset Include";
|
|
|
|
#################### PACKAGE-WIDE VARIABLES ###########################
|
|
my $debug_on; # is debugging on?
|
|
|
|
######
|
|
#Next text starts a new paragraph?
|
|
# $INP = 0 for plain text not starting a new paragraph
|
|
# $INP = 1 for text starting a new paragraph, so that we write a new
|
|
# \layout command and renew any font changes for the new paragraph
|
|
# Starts as 1 cuz if the first text in the document is plain text
|
|
# (not, e.g., in a \title command) it will start a new paragraph
|
|
my $IsNewParagraph = 1;
|
|
my $OldINP; #Save $IsNewParagraph during footnotes
|
|
|
|
# Some layouts may have no actual text in them before the next layout
|
|
# (e.g. slides). Pending Layout is set when we read a command that puts us
|
|
# in a new layout. If we get some regular text to print out, set it to false.
|
|
# But if we get to another layout command, first print out the command to
|
|
# start the pending layout.
|
|
my $PendingLayout = 0;
|
|
|
|
# HACK to protect spaces within optional argument to \item
|
|
my $protect_spaces = 0;
|
|
|
|
# $MBD = 1 if we're in a list, but haven't seen an '\item' command
|
|
# In that case, we may need to start a nested "Standard" paragraph
|
|
my $MayBeDeeper = 0;
|
|
my $OldMBD; #Save $MBD during footnotes -- this should very rarely be necessary!
|
|
|
|
# Stack to take care of environments like Enumerate, Quote
|
|
# We need a separate stack for footnotes, which have separate layouts etc.
|
|
# Therefore we use a reference to an array, not just an array
|
|
my @LayoutStack = ("Standard"); #default if everything else pops off
|
|
my $CurrentLayoutStack = \@LayoutStack;
|
|
|
|
# Status of various font commands
|
|
# Every font characteristic (family, series, etc.) needs a stack, because
|
|
# there may be nested font commands, like \textsf{blah \texttt{blah} blah}
|
|
# CurrentFontStatus usually points to the main %FontStatus hash, but
|
|
# when we're in a footnote, it will point to a temporary hash
|
|
my %FontStatus = (
|
|
'\emph' => ["default"],
|
|
'\family' => ["default"],
|
|
'\series' => ["default"],
|
|
'\shape' => ["default"],
|
|
'\bar' => ["default"],
|
|
'\size' => ["default"],
|
|
'\noun' => ["default"],
|
|
);
|
|
my $CurrentFontStatus = \%FontStatus;
|
|
|
|
# Currently aligning paragraphs right, left, or center?
|
|
my $CurrentAlignment = "";
|
|
my $OldAlignment; # Save $AS during footnotes
|
|
|
|
# Global variables for copying tex stuff
|
|
my $tex_mode_string; # string we accumulate tex mode stuff in
|
|
my @tex_mode_tokens; # stack of tokens which required calling copy_latex_known
|
|
|
|
# LyX strings to start and end TeX mode
|
|
my $start_tex_mode = "\n\\latex latex \n";
|
|
my $end_tex_mode = "\n\\latex default \n";
|
|
|
|
# String to write before each item
|
|
my $item_preface = "";
|
|
|
|
############# INFORMATION ABOUT LATEX AND LYX #############################
|
|
# LyX translations of LaTeX font commands
|
|
my %FontTransTable = (
|
|
# Font commands
|
|
'\emph' => "\n\\emph on \n",
|
|
'\underline' => "\n\\bar under \n",
|
|
'\underbar' => "\n\\bar under \n", # plain tex equivalent of underline?
|
|
'\textbf' => "\n\\series bold \n",
|
|
'\textmd' => "\n\\series medium \n",
|
|
'\textsf' => "\n\\family sans \n",
|
|
'\texttt' => "\n\\family typewriter \n",
|
|
'\textrm' => "\n\\family roman \n",
|
|
'\textsc' => "\n\\shape smallcaps \n",
|
|
'\textsl' => "\n\\shape slanted \n",
|
|
'\textit' => "\n\\shape italic \n",
|
|
'\textup' => "\n\\shape up \n",
|
|
'\noun' => "\n\\noun on \n", # LyX abstraction of smallcaps
|
|
|
|
# Font size commands
|
|
'\tiny' => "\n\\size tiny \n",
|
|
'\scriptsize' => "\n\\size scriptsize \n",
|
|
'\footnotesize' => "\n\\size footnotesize \n",
|
|
'\small' => "\n\\size small \n",
|
|
'\normalsize' => "\n\\size default \n",
|
|
'\large' => "\n\\size large \n",
|
|
'\Large' => "\n\\size Large \n",
|
|
'\LARGE' => "\n\\size LARGE \n",
|
|
'\huge' => "\n\\size huge \n",
|
|
'\Huge' => "\n\\size Huge \n",
|
|
# This doesn't work yet!
|
|
#'\textnormal' => "\n\\series medium \n\\family roman \n\\shape up \n",
|
|
);
|
|
|
|
# Things LyX implements as "Floats"
|
|
my %FloatTransTable = (
|
|
# Footnote, Margin note
|
|
'\footnote' => "\n\\begin_float footnote \n",
|
|
'\thanks' => "\n\\begin_float footnote \n", # thanks is same as footnote
|
|
'\marginpar' => "\n\\begin_float margin \n",
|
|
);
|
|
# Environments that LyX implements as "floats"
|
|
my %FloatEnvTransTable = (
|
|
"table" => "\n\\begin_float tab \n",
|
|
"table*" => "\n\\begin_float wide-tab \n",
|
|
"figure" => "\n\\begin_float fig \n",
|
|
"figure*" => "\n\\begin_float wide-fig \n",
|
|
);
|
|
|
|
# Simple LaTeX tokens which are turned into small pieces of LyX text
|
|
my %TextTokenTransTable = (
|
|
# LaTeX escaped characters
|
|
'\_' => '_',
|
|
'\%' => '%',
|
|
'\$' => '$',
|
|
'\&' => '&',
|
|
'\{' => '{',
|
|
'\}' => '}',
|
|
'\#' => '#',
|
|
'\~' => '~',
|
|
'\^' => '^',
|
|
# \i and \j are used in accents. LyX doesn't recognize them in plain
|
|
# text. Hopefully, this isn't a problem.
|
|
'\i' => '\i',
|
|
'\j' => '\j',
|
|
|
|
# Misc simple LaTeX tokens
|
|
'~' => "\n\\protected_separator \n",
|
|
'@' => "@", # TeX.pm considers this a token, but it's not really
|
|
'\@' => "\\SpecialChar \\@",
|
|
'\ldots' => "\n\\SpecialChar \\ldots\{\}\n",
|
|
'\-' => "\\SpecialChar \\-\n",
|
|
'\LaTeX' => "LaTeX",
|
|
'\LaTeXe' => "LaTeX2e",
|
|
'\TeX' => "TeX",
|
|
'\LyX' => "LyX",
|
|
'\lyxarrow' => "\\SpecialChar \\menuseparator\n",
|
|
'\hfill' => "\n\\hfill \n",
|
|
'\noindent' => "\n\\noindent \n",
|
|
'\textbackslash' => "\n\\backslash \n",
|
|
'\textgreater' => ">",
|
|
'\textless' => "<",
|
|
'\textbar' => "|",
|
|
'\textasciitilde' => "~",
|
|
);
|
|
|
|
# LyX translations of some plain LaTeX text (TeX parser won't recognize it
|
|
# as a Token, so we need to translate the Text::TeX::Text token.)
|
|
my %TextTransTable = (
|
|
# Double quotes
|
|
"``" => "\n\\begin_inset Quotes eld\n\\end_inset \n\n",
|
|
"''" => "\n\\begin_inset Quotes erd\n\\end_inset \n\n",
|
|
|
|
# Tokens that don't start with a backslash, so parser won't recognize them
|
|
# (LyX doesn't support them, so we just print them in TeX mode)
|
|
'?`' => "$start_tex_mode?`$end_tex_mode",
|
|
'!`' => "$start_tex_mode!`$end_tex_mode",
|
|
);
|
|
|
|
# Things that LyX translates as "LatexCommand"s
|
|
# E.g., \ref{foo} ->'\begin_inset LatexCommand \ref{foo}\n\n\end_inset \n'
|
|
# (Some take arguments, others don't)
|
|
my @LatexCommands = map {"\\$_"} qw(ref pageref label cite bibliography
|
|
index printindex tableofcontents
|
|
listofalgorithms listoftables listoffigures);
|
|
my @IncludeCommands = map {"\\$_"} qw(input include);
|
|
# Included postscript files
|
|
# LyX 1.0 can't do \includegraphics*!
|
|
my @GraphicsCommands = map {"\\$_"} qw(epsf epsffile epsfbox
|
|
psfig epsfig includegraphics);
|
|
|
|
# Accents. Most of these take an argument -- the thing to accent
|
|
# (\l and \L are handled as InsetLatexAccents, so they go here too)
|
|
my $AccentTokens = "\\\\[`'^#~=.bcdHklLrtuv\"]";
|
|
|
|
# Environments which describe justifying (converted to LyX \align commands)
|
|
# and the corresponding LyX commands
|
|
my %AlignEnvironments = (
|
|
"center" => "\n\\align center \n",
|
|
"flushright" => "\n\\align right \n",
|
|
"flushleft" => "\n\\align left \n",
|
|
);
|
|
|
|
# Some environments' begin commands take an extra argument
|
|
# Print string followed by arg for each item in the list, or ignore the arg ("")
|
|
my %ExtraArgEnvironments = (
|
|
"thebibliography" => "",
|
|
"lyxlist" =>'\labelwidthstring ',
|
|
"labeling" =>'\labelwidthstring ', # koma script list
|
|
);
|
|
|
|
# Math environments are copied verbatim
|
|
my $MathEnvironments = "(math|displaymath|xxalignat|(equation|eqnarray|align|alignat|xalignat|multline|gather)(\\*)?)";
|
|
# ListLayouts may have standard paragraphs nested inside them.
|
|
my $ListLayouts = "Itemize|Enumerate|Description";
|
|
|
|
##################### PARSER INVOCATION ##################################
|
|
sub call_parser {
|
|
# This subroutine calls the TeX parser & translator
|
|
# Before it does that, it does lots of setup work to get ready for parsing.
|
|
# Arg0 is the file to read (clean) LaTeX from
|
|
# Arg1 is the file to write LyX to
|
|
# Arg2 is the file to read layouts from (e.g., in LYX_DIR/layouts/foo.layout)
|
|
|
|
my ($InFileName, $OutFileName) = (shift,shift);
|
|
|
|
# Before anything else, set the package-wide variables based on the
|
|
# user-given flags
|
|
# opt_d is set to 1 if '-d' option given, else (probably) undefined
|
|
$debug_on = (defined($main::opt_d) && $main::opt_d);
|
|
|
|
# Hash of tokens passed to the TeX parser
|
|
# Many values are $Text::TeX::Tokens{'\operatorname'}, which has
|
|
# Type=>report_args and count=>1
|
|
# Note that we don't have to bother putting in tokens which will be simply
|
|
# translated (e.g., from %TextTokenTransTable).
|
|
my %MyTokens = (
|
|
'{' => $Text::TeX::Tokens{'{'},
|
|
'}' => $Text::TeX::Tokens{'}'},
|
|
'\begin' => $Text::TeX::Tokens{'\begin'},
|
|
'\end' => $Text::TeX::Tokens{'\end'},
|
|
|
|
# Lots of other commands will be made by ReadCommands:Merge
|
|
# by reading the LaTeX syntax file
|
|
|
|
# Font sizing commands (local)
|
|
'\tiny' => {Type => 'local'},
|
|
'\small' => {Type => 'local'},
|
|
'\scriptsize' => {Type => 'local'},
|
|
'\footnotesize' => {Type => 'local'},
|
|
'\small' => {Type => 'local'},
|
|
'\normalsize' => {Type => 'local'},
|
|
'\large' => {Type => 'local'},
|
|
'\Large' => {Type => 'local'},
|
|
'\LARGE' => {Type => 'local'},
|
|
'\huge' => {Type => 'local'},
|
|
'\Huge' => {Type => 'local'},
|
|
|
|
# Tokens to ignore (which make a new paragraph)
|
|
# Just pretend they actually ARE new paragraph markers!
|
|
'\maketitle' => {'class' => 'Text::TeX::Paragraph'},
|
|
);
|
|
|
|
# Now add to MyTokens all of the commands that were read from the
|
|
# commands file by package ReadCommands
|
|
&ReadCommands::Merge(\%MyTokens);
|
|
|
|
# Here's the actual subroutine. The above is all preparation
|
|
# Output LyX file
|
|
my $zzz = $debug_on ? " ($InFileName --> $OutFileName)\n" : "... ";
|
|
print STDERR "Translating$zzz";
|
|
open (OUTFILE,">$OutFileName");
|
|
|
|
# Open the file to turn into LyX.
|
|
my $infile = new Text::TeX::OpenFile $InFileName,
|
|
'defaultact' => \&basic_lyx,
|
|
'tokens' => \%MyTokens;
|
|
|
|
# Process what's left of the file (everything from \begin{document})
|
|
$infile->process;
|
|
|
|
# Last line of the LyX file
|
|
print OUTFILE "\n\\the_end\n";
|
|
close OUTFILE;
|
|
#warn "Done with basic translation\n";
|
|
return;
|
|
} # end subroutine call_parser
|
|
|
|
########################## MAIN TRANSLATOR SUBROUTINE #####################
|
|
sub basic_lyx {
|
|
# This subroutine is called by Text::TeX::process each time subroutine
|
|
# eat returns a value.
|
|
# Argument 0 is the return value from subroutine eat
|
|
# Argument 1 is the Text::TeX::OpenFile (namely, $TeXfile)
|
|
my $eaten = shift;
|
|
my $fileobject = shift;
|
|
|
|
# This handles most but maybe not all comments
|
|
# THere shouldn't be any if we've run CleanTeX.pl
|
|
print "Comment: ",$eaten->comment if defined $eaten->comment && $debug_on;
|
|
|
|
my $type = ref($eaten);
|
|
print "$type " if $debug_on;
|
|
|
|
# This loop is basically just a switch. However, making it a for
|
|
# (1) makes $_ = $type (convenient)
|
|
# (2) allows us to use things like next and last
|
|
TYPESW: for ($type) {
|
|
|
|
# some pre-case work
|
|
s/^Text::TeX:://o or die "unknown token?!";
|
|
my ($dummy, $tok);
|
|
my ($thistable);
|
|
|
|
# The parser puts whitespace before certain kinds of tokens along
|
|
# with that token. If that happens, save a space
|
|
my $pre_space = ""; # whitespace before a token
|
|
if (/BegArgsToken|^Token|::Group$/) {
|
|
$dummy = $eaten->exact_print;
|
|
# Only set prespace if we match something
|
|
# We wouldn't want it to be more than one space, since that's
|
|
# illegal in LyX. Also, replace \t or \n with ' ' since they are
|
|
# ignored in LyX. Hopefully, this won't lead to anything worse
|
|
# than some lines with >80 chars
|
|
# Finally, don't put a space at the beginning of a new paragraph
|
|
if (($dummy =~ /^\s+/) && !$IsNewParagraph) {
|
|
$pre_space = " ";
|
|
}
|
|
}
|
|
|
|
# Handle blank lines.
|
|
if (m/^Paragraph$/o) {
|
|
# $INP <>0 means We will need a new layout command
|
|
$IsNewParagraph = 1;
|
|
|
|
# $MBD means start a begin_deeper within list environments
|
|
# unless there's an \item command
|
|
$MayBeDeeper = 1;
|
|
|
|
last TYPESW;
|
|
}
|
|
|
|
# If, e.g., there's just a comment in this token, don't do anything
|
|
# This actually shouldn't happen if CleanTeX has already removed them
|
|
last TYPESW if !defined $eaten->print;
|
|
|
|
# Handle LaTeX tokens
|
|
if (/^Token$/o) {
|
|
|
|
my $name = $eaten->token_name; # name of the token, e.g., "\large"
|
|
print "'$name' " if $debug_on;
|
|
|
|
# Tokens which turn into a bit of LyX text
|
|
if (exists $TextTokenTransTable{$name}) {
|
|
&CheckForNewParagraph; #Start new paragraph if necessary
|
|
|
|
my $to_print = $TextTokenTransTable{$name};
|
|
|
|
# \@ has to be specially handled, because it depends on
|
|
# the character AFTER the \@
|
|
if ($name eq '\@') {
|
|
my $next = $fileobject->eatGroup(1);
|
|
my $ch="";
|
|
$ch = $next->print or warn "\@ confused me!\n";
|
|
if ($ch eq '.') {
|
|
# Note: \@ CAN'T have pre_space before it
|
|
print OUTFILE "$to_print$ch\n";
|
|
print "followed by $ch" if $debug_on;
|
|
} else {
|
|
warn "LyX (or LaTeX) can't handle '$ch' after $name\n";
|
|
print OUTFILE $ch;
|
|
}
|
|
|
|
} else { # it's not \@
|
|
# Print the translated text (include preceding whitespace)
|
|
print OUTFILE "$pre_space$to_print";
|
|
} # end special handling for \@
|
|
|
|
# Handle tokens that LyX translates as a "LatexCommand" inset
|
|
} elsif (grep {$_ eq $name} @LatexCommands) {
|
|
&CheckForNewParagraph; #Start new paragraph if necessary
|
|
print OUTFILE "$pre_space\n\\begin_inset LatexCommand ",
|
|
$name,
|
|
"\n\n\\end_inset \n\n";
|
|
|
|
# Math -- copy verbatim until you're done
|
|
} elsif ($name eq '\(' || $name eq '\[') {
|
|
print "\nCopying math beginning with '$name'\n" if $debug_on;
|
|
# copy everything until end text
|
|
$dummy = &Verbatim::copy_verbatim($fileobject, $eaten);
|
|
$dummy = &fixmath($dummy); # convert '\sp' to '^', etc.
|
|
|
|
&CheckForNewParagraph; # math could be first thing in a par
|
|
print OUTFILE "$pre_space\n\\begin_inset Formula $name ";
|
|
print $dummy if $debug_on;
|
|
print OUTFILE $dummy;
|
|
|
|
} elsif ($name eq '\)' || $name eq '\]') {
|
|
# end math
|
|
print OUTFILE "$name\n\\end_inset \n\n";
|
|
print "\nDone copying math ending with '$name'" if $debug_on;
|
|
|
|
# Items in list environments
|
|
} elsif ($name eq '\item') {
|
|
|
|
# What if we had a nested "Standard" paragraph?
|
|
# Then write \end_deeper to finish the standard layout
|
|
# before we write the new \layout ListEnv command
|
|
if ($$CurrentLayoutStack[-1] eq "Standard") {
|
|
pop (@$CurrentLayoutStack); # take "Standard" off the stack
|
|
print OUTFILE "\n\\end_deeper ";
|
|
print "\nCurrent Layout Stack: @$CurrentLayoutStack"
|
|
if $debug_on;
|
|
} # end deeper if
|
|
|
|
# Upcoming text (the item) will be a new paragraph,
|
|
# requiring a new layout command based on whichever
|
|
# kind of list environment we're in
|
|
$IsNewParagraph = 1;
|
|
|
|
# But since we had an \item command, DON'T nest a
|
|
# deeper "Standard" paragraph in the list
|
|
$MayBeDeeper = 0;
|
|
|
|
# Check for an optional argument to \item
|
|
# If so, within the [] we need to protect spaces
|
|
# TODO: In fact, for description, if there's no [] or
|
|
# there's an empty [], then we need to write a ~, since LyX
|
|
# will otherwise make the next word the label
|
|
# If it's NOT a description & has a [] then we're stuck!
|
|
# They need to fix the bullets w/in lyx!
|
|
if (($dummy = $fileobject->lookAheadToken) &&
|
|
($dummy =~ /\s*\[/)) {
|
|
$fileobject->eatFixedString('\['); # eat the [
|
|
$protect_spaces = 1;
|
|
}
|
|
|
|
# Special lists (e.g. List layout) have to print something
|
|
# before each item. In that case, CFNP and print it
|
|
if ($item_preface) {
|
|
&CheckForNewParagraph;
|
|
print OUTFILE $item_preface;
|
|
}
|
|
|
|
# Font sizing commands
|
|
# (Other font commands are TT::BegArgsTokens because they take
|
|
# arguments. Font sizing commands are 'local' TT::Tokens)
|
|
} elsif (exists $FontTransTable{$name}) {
|
|
my $command = $FontTransTable{$name}; #e.g., '\size large'
|
|
|
|
if (! $IsNewParagraph) {
|
|
print OUTFILE "$pre_space$command";
|
|
} #otherwise, wait until we've printed the new paragraph command
|
|
|
|
# Store the current font change
|
|
($dummy = $command) =~ s/\s*(\S+)\s+(\w+)\s*/$1/;
|
|
die "Font command error" if !exists $$CurrentFontStatus{$dummy};
|
|
push (@{$CurrentFontStatus->{$dummy}}, $2);
|
|
print "\nCurrent $dummy Stack: @{$CurrentFontStatus->{$dummy}}"
|
|
if $debug_on;
|
|
|
|
# Table stuff
|
|
} elsif ($name eq '&') {
|
|
if ($thistable = &RelyxTable::in_table) {
|
|
print OUTFILE "\n\\newline \n";
|
|
$thistable->nextcol;
|
|
} else {warn "& is illegal outside a table!"}
|
|
|
|
} elsif ($name eq '\\\\' || $name eq '\\newline') {
|
|
&CheckForNewParagraph; # could be at beginning of par?
|
|
print OUTFILE "\n\\newline \n";
|
|
|
|
# If we're in a table, \\ means add a row to the table
|
|
# Note: if we're on the last row of the table, this extra
|
|
# row will get deleted later. This hack is necessary, because
|
|
# we don't know while reading when the last row is!
|
|
if ($thistable = &RelyxTable::in_table) {
|
|
$thistable->addrow;
|
|
}
|
|
|
|
} elsif ($name eq '\hline') {
|
|
if ($thistable = &RelyxTable::in_table) {
|
|
# hcline does hline if no arg is given
|
|
$thistable->hcline;
|
|
} else {warn "\\hline is illegal outside a table!"}
|
|
|
|
# Figures
|
|
|
|
} elsif ($name =~ /^\\epsf[xy]size$/) {
|
|
# We need to eat '=' followed by EITHER more text OR
|
|
# one (or more?!) macros describing a TeX size
|
|
my $arg = $fileobject->eatMultiToken;
|
|
my $length = $arg->print;
|
|
$length =~ s/^\s*=\s*// or warn "weird '$name' command!";
|
|
|
|
# If there's no "cm" or other letters in $length, the next token
|
|
# ought to be something like \textwidth. Then it will be empty
|
|
# or just have numbers in it.
|
|
# This is bugprone. Hopefully not too many people use epsf!
|
|
if ($length =~ /^[\d.]*\s*$/) {
|
|
my $next = $fileobject->eatMultiToken;
|
|
$length .= $next->print;
|
|
}
|
|
$length =~ s/\s*$//; # may have \n at end
|
|
|
|
# If we can't parse the command, print it in tex mode
|
|
&RelyxFigure::parse_epsfsize($name, $length) or
|
|
&print_tex_mode("$name=$length");
|
|
|
|
# Miscellaneous...
|
|
|
|
} elsif ($name =~ /\\verb.*?/) {
|
|
my $dummy = &Verbatim::copy_verb($fileobject, $eaten);
|
|
print "\nCopying $name in TeX mode: " if $debug_on;
|
|
&print_tex_mode ($dummy);
|
|
|
|
# Otherwise it's an unknown token, which must be copied
|
|
# in TeX mode, along with its arguments, if any
|
|
} else {
|
|
if (defined($eaten->relyx_args($fileobject))) {
|
|
©_latex_known($eaten, $fileobject);
|
|
} else { # it's not in MyTokens
|
|
©_latex_unknown($eaten, $fileobject);
|
|
}
|
|
}
|
|
|
|
last TYPESW;
|
|
}
|
|
|
|
# Handle tokens that take arguments, like \section{},\section*{}
|
|
if (/^BegArgsToken$/) {
|
|
my $name = $eaten->token_name;
|
|
print "$name" if $debug_on;
|
|
|
|
# Handle things that LyX translates as a "LatexCommand" inset
|
|
if (grep {$_ eq $name} @LatexCommands) {
|
|
&CheckForNewParagraph; #Start new paragraph if necessary
|
|
|
|
print OUTFILE "$pre_space\n\\begin_inset LatexCommand ";
|
|
|
|
# \bibliography gets handled as a LatexCommand inset, but
|
|
# it's a special case, cuz LyX syntax expects "\BibTeX"
|
|
# instead of "\bibliography" (I have no idea why), and because
|
|
# we have to print extra stuff
|
|
# Because we might not have encountered the
|
|
# \bibliographystyle command yet, we write
|
|
# "insert bibstyle here", and replace that string
|
|
# with the actual bibliographystyle argument in
|
|
# LastLyX (i.e., the next pass through the file)
|
|
if ($name eq "\\bibliography") {
|
|
print OUTFILE "\\BibTeX[", $bibstyle_insert_string, "]";
|
|
} else {
|
|
print OUTFILE "$name";
|
|
}
|
|
|
|
# \cite takes an optional argument, e.g.
|
|
my $args = $eaten->relyx_args ($fileobject);
|
|
while ($args =~ s/^o//) {
|
|
my $tok = $fileobject->eatOptionalArgument;
|
|
my $dummy = $tok->exact_print;
|
|
print OUTFILE $dummy;
|
|
}
|
|
|
|
print OUTFILE "\{";
|
|
last TYPESW; # skip to the end of the switch
|
|
}
|
|
|
|
if (grep {$_ eq $name} @IncludeCommands) {
|
|
&CheckForNewParagraph; #Start new paragraph if necessary
|
|
print OUTFILE "$pre_space\n$Begin_Inset_Include $name\{";
|
|
last TYPESW; # skip to the end of the switch
|
|
}
|
|
|
|
# This is to handle cases where _ is used, say, in a filename.
|
|
# When _ is used in math mode, it'll be copied by the math mode
|
|
# copying subs. Here we handle cases where it's used in non-math.
|
|
# Examples are filenames for \include & citation labels.
|
|
# (It's illegal to use it in regular LaTeX text.)
|
|
if ($name eq "_") {
|
|
print OUTFILE $eaten->exact_print;
|
|
last TYPESW;
|
|
}
|
|
|
|
# Sectioning and Title environments (using a LyX \layout command)
|
|
if (exists $ReadCommands::ToLayout->{$name}) {
|
|
&ConvertToLayout($name);
|
|
last TYPESW; #done translating
|
|
|
|
# Font characteristics
|
|
} elsif (exists $FontTransTable{$name}) {
|
|
my $dum2;
|
|
my $command = $FontTransTable{$name};
|
|
($dummy, $dum2) = ($command =~ /(\S+)\s+(\w+)/);
|
|
|
|
# HACK so that "\emph{hi \emph{bye}}" yields unemph'ed "bye"
|
|
if ( ($dummy eq "\\emph") &&
|
|
($CurrentFontStatus->{$dummy}->[-1] eq "on")) {
|
|
$dum2 = "default"; # "on" instead of default?
|
|
$command =~ s/on/default/;
|
|
}
|
|
|
|
# If we're about to start a new paragraph, save writing
|
|
# this command until *after* we write '\layout Standard'
|
|
if (! $IsNewParagraph) {
|
|
print OUTFILE "$pre_space$command";
|
|
}
|
|
|
|
# Store the current font change
|
|
die "Font command error" if !exists $$CurrentFontStatus{$dummy};
|
|
push (@{$CurrentFontStatus->{$dummy}}, $dum2);
|
|
|
|
|
|
# Handle footnotes and margin notes
|
|
# Make a new font table & layout stack which will be local to the
|
|
# footnote or marginpar
|
|
} elsif (exists $FloatTransTable{$name}) {
|
|
my $command = $FloatTransTable{$name};
|
|
|
|
# Open the footnote
|
|
print OUTFILE "$pre_space$command";
|
|
|
|
# Make $CurrentFontStatus point to a new (anonymous) font table
|
|
$CurrentFontStatus = {
|
|
'\emph' => ["default"],
|
|
'\family' => ["default"],
|
|
'\series' => ["default"],
|
|
'\shape' => ["default"],
|
|
'\bar' => ["default"],
|
|
'\size' => ["default"],
|
|
'\noun' => ["default"],
|
|
};
|
|
|
|
# And make $CurrentLayoutStack point to a new (anon.) stack
|
|
$CurrentLayoutStack = ["Standard"];
|
|
|
|
# Store whether we're at the end of a paragraph or not
|
|
# for when we get to end of footnote AND
|
|
# Note that the footnote text will be starting a new paragraph
|
|
# Also store the current alignment (justification)
|
|
$OldINP = $IsNewParagraph; $OldMBD = $MayBeDeeper;
|
|
$OldAlignment = $CurrentAlignment;
|
|
$IsNewParagraph = 1;
|
|
$MayBeDeeper = 0; #can't be deeper at beginning of footnote
|
|
$CurrentAlignment = "";
|
|
|
|
# Accents
|
|
} elsif ($name =~ m/^$AccentTokens$/) {
|
|
&CheckForNewParagraph; # may be at the beginning of a par
|
|
|
|
print OUTFILE "$pre_space\n",'\i ',$name,'{'
|
|
|
|
# Included Postscript Figures
|
|
# Currently, all postscript including commands take one
|
|
# required argument and 0 to 2 optional args, so we can
|
|
# clump them together in one else.
|
|
} elsif (grep {$_ eq $name} @GraphicsCommands) {
|
|
&CheckForNewParagraph; # may be at the beginning of a par
|
|
my $arg1 = $fileobject->eatOptionalArgument;
|
|
# arg2 is a token of an empty string for most commands
|
|
my $arg2 = $fileobject->eatOptionalArgument;
|
|
my $arg3 = $fileobject->eatRequiredArgument;
|
|
my $save = $arg1->exact_print . $arg2->exact_print .
|
|
$arg3->exact_print;
|
|
|
|
# Parse and put figure into LyX file
|
|
# Print it verbatim if we didn't parse correctly
|
|
my $thisfig = new RelyxFigure::Figure;
|
|
if ($thisfig->parse_pscommand($name, $arg1, $arg2, $arg3)) {
|
|
print OUTFILE $thisfig->print_info;
|
|
} else {
|
|
&print_tex_mode($eaten->exact_print . $save);
|
|
}
|
|
|
|
# Tables
|
|
|
|
} elsif ($name eq "\\multicolumn") {
|
|
if ($thistable = &RelyxTable::in_table) {
|
|
# the (simple text) first arg.
|
|
$dummy = $fileobject->eatRequiredArgument->contents->print;
|
|
my $group = $fileobject->eatRequiredArgument;
|
|
$thistable->multicolumn($dummy, $group);
|
|
} else {warn "\\multicolumn is illegal outside a table!"}
|
|
|
|
} elsif ($name eq '\cline') {
|
|
if ($thistable = &RelyxTable::in_table) {
|
|
# the (simple text) first arg.
|
|
$dummy = $fileobject->eatRequiredArgument->contents->print;
|
|
# sub hcline does cline if an arg is given
|
|
$thistable->hcline($dummy);
|
|
} else {warn "\\cline is illegal outside a table!"}
|
|
|
|
# Bibliography
|
|
|
|
} elsif ($name eq '\bibliographystyle') {
|
|
$tok = $fileobject->eatRequiredArgument;
|
|
$bibstyle_file = "";
|
|
# There may be >1 token in the {}, e.g. "{a_b}" -> 3 tokens
|
|
my @toks = $tok->contents;
|
|
foreach $tok (@toks) {
|
|
# kludge: CleanTeX adds {} after _
|
|
$tok = $tok->contents if ref($tok) eq "Text::TeX::Group";
|
|
$bibstyle_file .= $tok->print;
|
|
}
|
|
print "\nBibliography style file is $bibstyle_file"if $debug_on;
|
|
|
|
# LyX \bibitem actually looks just like LaTeX bibitem, except
|
|
# it's in a Bibliography par & there must be a space after the
|
|
# bibitem command. Note we need to explicitly print the braces...
|
|
} elsif ($name eq "\\bibitem") {
|
|
$IsNewParagraph=1; # \bibitem always starts new par. in LyX
|
|
&CheckForNewParagraph;
|
|
|
|
$tok = $fileobject->eatOptionalArgument;
|
|
print OUTFILE "$name ", $tok->exact_print, "{";
|
|
|
|
# Miscellaneous
|
|
|
|
# ensuremath -- copy verbatim until you're done
|
|
# but add \( and \)
|
|
# Note that we'll only get here if the command is NOT in math mode
|
|
} elsif ($name eq '\ensuremath') {
|
|
print "\nCopying math beginning with '$name'\n" if $debug_on;
|
|
my $tok = $fileobject->eatGroup; # eat math expression
|
|
my $dummy = $tok->exact_print;
|
|
$dummy =~ s/\{(.*)\}/$1/;
|
|
$dummy = &fixmath($dummy); # convert '\sp' to '^', etc.
|
|
|
|
&CheckForNewParagraph; # math could be first thing in a par
|
|
print OUTFILE "$pre_space\n\\begin_inset Formula \\( ";
|
|
print $dummy if $debug_on;
|
|
print OUTFILE $dummy;
|
|
|
|
# end math
|
|
print OUTFILE "\\)\n\\end_inset \n\n";
|
|
print "\nDone copying math" if $debug_on;
|
|
|
|
# Token in the ReadCommands command list that basic_lyx doesn't
|
|
# know how to handle
|
|
} else {
|
|
©_latex_known($eaten,$fileobject);
|
|
} # end big if
|
|
|
|
# Exit the switch
|
|
last TYPESW;
|
|
}
|
|
|
|
# ArgTokens appear when we've used eatRequiredArgument
|
|
if (/^ArgToken$/) {
|
|
# If we're copying a recognized but untranslatable token in tex mode
|
|
my $tok = $tex_mode_tokens[-1] || 0;
|
|
if ($eaten->base_token == $tok) {
|
|
©_latex_known($eaten,$fileobject);
|
|
}
|
|
|
|
last TYPESW;
|
|
}
|
|
|
|
if (/^EndArgsToken$/) {
|
|
# If we're copying a recognized but untranslatable token in tex mode
|
|
my $tok = $tex_mode_tokens[-1] || 0;
|
|
if ($eaten->base_token eq $tok) {
|
|
©_latex_known($eaten,$fileobject);
|
|
last TYPESW;
|
|
}
|
|
|
|
my $name = $eaten->token_name;
|
|
print "$name" if $debug_on;
|
|
|
|
# Handle things that LyX translates as a "LatexCommand" inset
|
|
# or "Include" insets
|
|
if (grep {$_ eq $name} @LatexCommands, @IncludeCommands) {
|
|
print OUTFILE "\}\n\n\\end_inset \n\n";
|
|
|
|
} elsif (exists $ReadCommands::ToLayout->{$name}) {
|
|
&EndLayout($name);
|
|
|
|
# Font characteristics
|
|
# Pop the current FontStatus stack for a given characteristic
|
|
# and give the new command (e.g., \emph default)
|
|
} elsif (exists $FontTransTable{$name}) {
|
|
my $command = $FontTransTable{$name};
|
|
($dummy) = ($command =~ /(\S+)\s+\w+/);
|
|
pop @{$CurrentFontStatus->{$dummy}};
|
|
$command = "\n$dummy $CurrentFontStatus->{$dummy}->[-1] \n";
|
|
print OUTFILE "$command";
|
|
|
|
# Footnotes and marginpars
|
|
} elsif (exists $FloatTransTable{$name}) {
|
|
print OUTFILE "\n\\end_float \n\n";
|
|
|
|
# Reset the layout stack and font status table pointers to
|
|
# point to the global stack/table again, instead of the
|
|
# footnote-specific stack/table
|
|
$CurrentFontStatus = \%FontStatus;
|
|
$CurrentLayoutStack = \@LayoutStack;
|
|
|
|
# We need to reissue any font commands (but not layouts)
|
|
foreach $dummy (keys %$CurrentFontStatus) {
|
|
if ($CurrentFontStatus->{$dummy}->[-1] ne "default") {
|
|
print OUTFILE $FontTransTable{$dummy};
|
|
}
|
|
}
|
|
|
|
# Same paragraph status as we had before the footnote started
|
|
$IsNewParagraph = $OldINP; $MayBeDeeper = $OldMBD;
|
|
$CurrentAlignment = $OldAlignment;
|
|
|
|
} elsif ($name =~ m/^$AccentTokens$/) {
|
|
print OUTFILE "}\n";
|
|
|
|
} elsif ($name eq "\\bibitem") {
|
|
print OUTFILE "}\n";
|
|
} # End if on $name
|
|
|
|
# Exit main switch
|
|
last TYPESW;
|
|
} # end if EndArgsToken
|
|
|
|
# Handle END of scope of local commands like \large
|
|
if (/^EndLocal$/) {
|
|
my $name = $eaten->token_name; #cmd we're ending, e.g.,\large
|
|
print $name if $debug_on;
|
|
|
|
if (exists $FontTransTable{$name}) {
|
|
my $command = $FontTransTable{$name};
|
|
($dummy = $command) =~ s/\s*(\S*)\s+(\w+)\s*/$1/; #e.g., '\size'
|
|
die "Font command error" if !exists $$CurrentFontStatus{$dummy};
|
|
# TT::OF->check_presynthetic returns local commands FIFO!
|
|
# So pop font stack, but warn if we pop the wrong thing
|
|
warn " font confusion?" if
|
|
pop @{$CurrentFontStatus->{$dummy}} ne $2;
|
|
print "\nCurrent $dummy Stack: @{$CurrentFontStatus->{$dummy}}"
|
|
if $debug_on;
|
|
my $newfont = $CurrentFontStatus->{$dummy}->[-1];
|
|
$command = "\n$dummy $newfont\n";
|
|
print OUTFILE "$command";
|
|
|
|
} else {
|
|
warn "Unknown EndLocal token!\n";
|
|
}
|
|
|
|
last TYPESW;
|
|
}
|
|
|
|
# We don't print { or }, but we make sure that the spacing is correct
|
|
# Handle '{'
|
|
if (/^Begin::Group$/) {
|
|
print OUTFILE "$pre_space";
|
|
last TYPESW;
|
|
}
|
|
|
|
# Handle '{'
|
|
if (/^End::Group$/) {
|
|
print OUTFILE "$pre_space";
|
|
last TYPESW;
|
|
}
|
|
|
|
# Handle \begin{foo}
|
|
if (/^Begin::Group::Args$/) {
|
|
print $eaten->print," " if $debug_on; # the string "\begin{foo}"
|
|
my $env = $eaten->environment;
|
|
|
|
# Any environment found in the layouts files
|
|
if (exists $ReadCommands::ToLayout->{$env}) {
|
|
&ConvertToLayout($env);
|
|
|
|
# Some environments have an extra argument. In that case,
|
|
# print the \layout command (cuz these environments always
|
|
# start new pars). Then either print the extra arg or
|
|
# ignore it (depending on the environment).
|
|
if (exists $ExtraArgEnvironments{$env}) {
|
|
# Should be just one token in the arg.
|
|
my $arg = $fileobject->eatBalanced->contents->print;
|
|
|
|
if ($ExtraArgEnvironments{$env}) { #print it
|
|
print "\nArgument $arg to $env environment"
|
|
if $debug_on;
|
|
$item_preface = $ExtraArgEnvironments{$env} . $arg."\n";
|
|
|
|
} else { #ignore it
|
|
print "\nIgnoring argument '$arg' to $env environment"
|
|
if $debug_on;
|
|
}
|
|
} # end if for reading extra args to \begin command
|
|
|
|
# Math environments
|
|
} elsif ($env =~ /^$MathEnvironments$/o) {
|
|
&CheckForNewParagraph; # may be beginning of paragraph
|
|
my $begin_text = $eaten->print;
|
|
print "\nCopying math beginning with '$begin_text'\n"
|
|
if $debug_on;
|
|
print OUTFILE "\n\\begin_inset Formula $begin_text ";
|
|
$dummy = &Verbatim::copy_verbatim($fileobject, $eaten);
|
|
$dummy = &fixmath($dummy); # convert '\sp' to '^', etc.
|
|
print $dummy if $debug_on;
|
|
print OUTFILE $dummy;
|
|
|
|
# Alignment environments
|
|
} elsif (exists $AlignEnvironments{$env}) {
|
|
# Set it to the command which creates this alignment
|
|
$CurrentAlignment = $AlignEnvironments{$env};
|
|
($dummy) = ($CurrentAlignment =~ /\S+\s+(\w+)/);
|
|
print "\nNow $dummy-aligning text " if $debug_on;
|
|
|
|
# alignment environments automatically start a new paragraph
|
|
$IsNewParagraph = 1;
|
|
|
|
# Environments lyx translates to floats
|
|
} elsif (exists $FloatEnvTransTable{$env}) {
|
|
# this really only matters if it's at the very
|
|
# beginning of the doc.
|
|
&CheckForNewParagraph;
|
|
|
|
$tok = $fileobject->eatOptionalArgument;
|
|
if ($tok && defined ($dummy = $tok->print) && $dummy) {
|
|
print "\nIgnoring float placement '$dummy'" if $debug_on;
|
|
}
|
|
my $command = $FloatEnvTransTable{$env};
|
|
|
|
# Open the table/figure
|
|
print OUTFILE "$command";
|
|
|
|
# table
|
|
} elsif ($env =~ /^tabular$/) { # don't allow tabular* or ctabular
|
|
# Table must start a new paragraph
|
|
$IsNewParagraph = 1; $MayBeDeeper = 1;
|
|
# We want to print table stuff *after* a \layout Standard
|
|
&CheckForNewParagraph;
|
|
|
|
# Since table info needs to come *before* the table content,
|
|
# put a line in the output file saying that the *next*
|
|
# reLyX pass needs to put the table info there
|
|
print OUTFILE "\n$RelyxTable::TableBeginString\n";
|
|
|
|
# Read and ignore an optional argument [t] or [b]
|
|
$tok = $fileobject->eatOptionalArgument;
|
|
if ($tok && defined ($dummy = $tok->print) && $dummy) {
|
|
print "\nIgnoring positioning arg '$dummy'" if $debug_on;
|
|
}
|
|
|
|
# Read the argument into a TT::Group
|
|
# (that group may contain groups, e.g. for {clp{10cm}}
|
|
$tok = $fileobject->eatGroup;
|
|
new RelyxTable::Table $tok;
|
|
|
|
# \begin document
|
|
} elsif ($env eq "document") {
|
|
# do nothing
|
|
#print "\nStarting to translate actual document" if $debug_on;
|
|
|
|
# Special environments to copy as regular text (-r option).
|
|
# Do this by copying the \begin & \end command in TeX mode
|
|
# (\Q\E around $env allows *'s in environment names!)
|
|
} elsif (grep /^\Q$env\E$/, @ReadCommands::regular_env) {
|
|
print "\nCopying $env environment as regular text\n"
|
|
if $debug_on;
|
|
$dummy = $eaten->print; # \begin{env}, ignore initial whitespace
|
|
&print_tex_mode($dummy);
|
|
|
|
# otherwise, it's an unknown environment
|
|
# In that case, copy everything up to the \end{env}
|
|
# Save stuff in global tex_mode_string so we can print it
|
|
# when we read & handle the \end{env}
|
|
} else {
|
|
|
|
print "\nUnknown environment $env" if $debug_on;
|
|
$tex_mode_string = "";
|
|
# print "\begin{env}
|
|
# For reLyXskip env, don't print the \begin & \end commands!
|
|
$tex_mode_string .= $eaten->exact_print
|
|
unless $env eq "reLyXskip";
|
|
$tex_mode_string .=&Verbatim::copy_verbatim($fileobject,$eaten);
|
|
}
|
|
|
|
last TYPESW;
|
|
}
|
|
|
|
# Handle \end{foo}
|
|
if (/^End::Group::Args$/) {
|
|
print $eaten->print," " if $debug_on; # the string "\end{foo}"
|
|
my $env = $eaten->environment;
|
|
|
|
# End of list or quote/verse environment
|
|
# OR special environment given with -t option
|
|
if (exists $ReadCommands::ToLayout->{$env}) {
|
|
&EndLayout($env);
|
|
$item_preface = ""; # reset when at end of List env.
|
|
|
|
# End of math environments
|
|
} elsif ($env =~ /^$MathEnvironments$/o) {
|
|
print OUTFILE "\\end{$env}\n\\end_inset \n\n";
|
|
print "\nDone copying math environment '$env'" if $debug_on;
|
|
|
|
} elsif (exists $AlignEnvironments{$env}) {
|
|
# Back to block alignment
|
|
$CurrentAlignment = "";
|
|
print "\nBack to block alignment" if $debug_on;
|
|
|
|
# assume that \end should end a paragraph
|
|
# This isn't correct LaTeX, but LyX can't align part of a par
|
|
$IsNewParagraph = 1;
|
|
|
|
# Environments lyx translates to floats
|
|
} elsif (exists $FloatEnvTransTable{$env}) {
|
|
print OUTFILE "\n\\end_float \n\n";
|
|
|
|
# table
|
|
} elsif ($env =~ /tabular$/) { # don't allow tabular*
|
|
if ($thistable = &RelyxTable::in_table) {
|
|
$thistable->done_reading;
|
|
print OUTFILE "\n$RelyxTable::TableEndString\n";
|
|
} else {warn "found \\end{tabular} when not in table!"}
|
|
|
|
# Anything after a table will be a new paragraph
|
|
$IsNewParagraph = 1; $MayBeDeeper = 1;
|
|
|
|
} elsif ($env eq "document") {
|
|
print "\nDone with document!" if $debug_on;
|
|
|
|
# "regular" environment given with -r option
|
|
} elsif (grep /^\Q$env\E$/, @ReadCommands::regular_env) {
|
|
$dummy = $eaten->print; # \end{env}, ignore initial whitespace
|
|
&print_tex_mode($dummy);
|
|
|
|
# Next stuff will be new env.
|
|
$IsNewParagraph = 1;
|
|
|
|
# End of unknown environments. We're already in TeX mode
|
|
} else {
|
|
# Add \end{env} (including initial whitespace) to string
|
|
# For reLyXskip environment, don't print \begin & \end commands!
|
|
$tex_mode_string .= $eaten->exact_print
|
|
unless $env eq "reLyXskip";
|
|
# Now print it
|
|
&print_tex_mode($tex_mode_string);
|
|
print "Done copying unknown environment '$env'" if $debug_on;
|
|
}
|
|
|
|
last TYPESW;
|
|
|
|
}
|
|
|
|
# Note for text handling: we have to do lots of stuff to handle
|
|
# spaces in (as close as possible to) the same way that LaTeX does
|
|
# LaTeX considers all whitespace to be the same, so basically, we
|
|
# convert each clump of whitespace to one space. Unfortunately, there
|
|
# are special cases, like whitespace at the beginning/end of a par,
|
|
# which we have to get rid of to avoid extra spaces in the LyX display.
|
|
# \n at the end of a paragraph must be considered like a space,
|
|
# because the next line might begin with a token like \LyX. But
|
|
# if the next line starts with \begin, say, then an extra space will be
|
|
# generated in the LyX file. Oh well. It doesn't affect the dvi file.
|
|
if (/^Text$/) {
|
|
my $outstr = $eaten->print; # the actual text
|
|
|
|
# don't bother printing whitespace
|
|
# Note: this avoids the problem of extra whitespace generating
|
|
# extra Text::TeX::Paragraphs, which would generate extra
|
|
# \layout commands
|
|
last TYPESW if $outstr =~ /^\s+$/;
|
|
|
|
# whitespace at beginning of a paragraph is meaningless
|
|
# e.g. \begin{foo}\n hello \end{foo} shouldn't print the \n
|
|
# (Note: check IsNewParagraph BEFORE calling &CFNP, which zeros it)
|
|
my $replace = $IsNewParagraph ? "" : " ";
|
|
$outstr =~ s/^\s+/$replace/;
|
|
|
|
# Only write '\layout Standard' once per paragraph
|
|
&CheckForNewParagraph;
|
|
|
|
# \n\n signals end of paragraph, so get rid of it (and any space
|
|
# before it)
|
|
$outstr =~ s/\s*\n\n$//;
|
|
|
|
# Print the LaTeX text to STDOUT
|
|
print "'$outstr'" if $debug_on;
|
|
|
|
# LyX *ignores* \n and \t, whereas LaTeX considers them just
|
|
# like a space.
|
|
# Also, many spaces are equivalent to one space in LaTeX
|
|
# (But LyX misleadingly displays them on screen, so get rid of them)
|
|
$outstr =~ s/\s+/ /g;
|
|
|
|
# protect spaces in an optional argument if necessary
|
|
# Put a SPACE after the argument for List, Description layouts
|
|
if ($protect_spaces) {
|
|
$dummy = $TextTokenTransTable{'~'};
|
|
|
|
# This will not handle brackets in braces!
|
|
if ($outstr =~ /\]/) { # protect spaces only *until* the bracket
|
|
my $tempstr = $`;
|
|
my $tempstr2 = $';
|
|
# Note that any \t's have been changed to space already
|
|
$tempstr =~ s/ /$dummy/g;
|
|
|
|
# Print 1 space after the argument (which finished with ])
|
|
# Don't print 2 (i.e. remove leading space from $tempstr2)
|
|
# don't print the bracket
|
|
$tempstr2 =~ s/^ //;
|
|
$outstr = "$tempstr $tempstr2";
|
|
$protect_spaces = 0; # Done with optional argument
|
|
} else { # protect all spaces, since we're inside brackets
|
|
$outstr =~ s/ /$dummy/g;
|
|
}
|
|
} # end special stuff for protected spaces
|
|
|
|
# Translate any LaTeX text that requires special LyX handling
|
|
foreach $dummy (keys %TextTransTable) {
|
|
$outstr =~ s/\Q$dummy\E/$TextTransTable{$dummy}/g;
|
|
}
|
|
|
|
# "pretty-print" the string. It's not perfect, since there may
|
|
# be text in the OUTFILE before $outstr, but it will keep from
|
|
# having REALLY long lines.
|
|
# Try to use approximately the same word-wrapping as LyX does:
|
|
# - before space after a period, except at end of string
|
|
# - before first space after column seventy-one
|
|
# - after 80'th column
|
|
while (1) {
|
|
$outstr =~ s/\. (?!$)/.\n / or
|
|
$outstr =~ s/(.{71,79}?) /$1\n / or
|
|
$outstr =~ s/(.{80})(.)/$1\n$2/ or
|
|
last; # exit loop if string is < 79 characters
|
|
}
|
|
|
|
# Actually print the text
|
|
print OUTFILE "$outstr";
|
|
last TYPESW;
|
|
} # end TT::Text handling
|
|
|
|
# The default action - this should never happen!
|
|
print("I don't know ",$eaten->print) if $debug_on;
|
|
|
|
} # end for ($type)
|
|
|
|
print "\n" if $debug_on;
|
|
|
|
} #end sub basic_lyx
|
|
|
|
######################### TEX MODE SUBROUTINES #########################
|
|
|
|
# This subroutine copies and prints a latex token and its arguments if any.
|
|
# This sub is only needed if the command was not found in the syntax file
|
|
# Use exact_print to preserve, e.g., whitespace after macros
|
|
sub copy_latex_unknown {
|
|
my $eaten = shift;
|
|
my $fileobject = shift;
|
|
my $outstr = $eaten->exact_print;
|
|
my ($dummy, $tok, $count);
|
|
|
|
# Copy the actual word. Copy while you've still got
|
|
# arguments. Assume all args must be in the same paragraph
|
|
# (There could be new paragraphs *within* args)
|
|
# We can't use copy_verbatim (unless we make it smarter) because
|
|
# it would choke on nested braces
|
|
print "\nUnknown token: '",$eaten->print,"': Copying in TeX mode\n"
|
|
if $debug_on;
|
|
my $dum2;
|
|
while (($dum2 = $fileobject->lookAheadToken) &&
|
|
($dum2 =~ /^[[{]$/)) {
|
|
if ($dum2 eq '[') { #copy optional argument - assume it's simple
|
|
$tok = $fileobject->eatOptionalArgument;
|
|
$outstr .= $tok->exact_print; # also print brackets & whitespace
|
|
} else {
|
|
$count = 0;
|
|
EAT: { #copied from eatBalanced, but allow paragraphs
|
|
die unless defined ($tok = $fileobject->eatMultiToken);
|
|
$outstr.="\n",redo EAT if ref($tok) eq "Text::TeX::Paragraph";
|
|
$dummy = $tok->exact_print;
|
|
$outstr .= $dummy;
|
|
# Sometimes, token will be '\n{', e.g.
|
|
$count++ if $dummy =~ /^\s*\{$/; # add a layer of nesting
|
|
$count-- if $dummy =~ /^\s*\}$/; # end one layer of nesting
|
|
redo EAT if $count; #don't dump out until all done nesting
|
|
} #end EAT block
|
|
} # end if $dummy = [{
|
|
|
|
} #end while
|
|
# Add {} after macro if it's followed by '}'. Otherwise, {\foo}bar
|
|
# will yield \foobar when LyX creates LaTeX files
|
|
$outstr.="{}" if $outstr=~/\\[a-zA-Z]+$/ && $dum2 eq '}';
|
|
|
|
# Print it out in TeX mode
|
|
&print_tex_mode($outstr);
|
|
|
|
print "\nDone copying unknown token" if $debug_on;
|
|
} # end sub copy_latex_unknown
|
|
|
|
# Copy an untranslatable latex command whose syntax we know, along with its
|
|
# arguments
|
|
# The command itself, optional arguments, and untranslatable
|
|
# arguments get copied in TeX mode. However, arguments which contain regular
|
|
# LaTeX will get translated by reLyX. Do that by printing what you have so
|
|
# far in TeX mode, leaving this subroutine, continuing with regular reLyX
|
|
# translating, and then returning here when you reach the ArgToken or
|
|
# EndArgsToken at the end of the translatable argument.
|
|
# We need to keep a stack of the tokens that brought us here, because
|
|
# you might have nested commands (e.g., \mbox{hello \fbox{there} how are you}
|
|
sub copy_latex_known {
|
|
my ($eaten, $fileobject) = (shift,shift);
|
|
my $type = ref($eaten);
|
|
$type =~ s/^Text::TeX::// or die "unknown token?!";
|
|
|
|
# token itself for TT::Token, TT::BegArgsToken,
|
|
# Corresponding BegArgsToken for ArgToken,EndArgsToken
|
|
my $temp_start = $eaten->base_token;
|
|
|
|
# Initialize tex mode copying
|
|
if ($type eq "BegArgsToken" or $type eq "Token") {
|
|
print "\nCopying untranslatable token '",$eaten->print,
|
|
"' in TeX mode" if $debug_on;
|
|
push @tex_mode_tokens, $temp_start;
|
|
|
|
# initialize the string of stuff we're copying
|
|
$tex_mode_string = $eaten->exact_print;
|
|
} # Start tex copying?
|
|
|
|
# Handle arguments
|
|
# This token's next arguments -- returns a string matching /o*[rR]?/
|
|
my $curr_args = $eaten->next_args($fileobject);
|
|
|
|
if ($type eq "EndArgsToken" or $type eq "ArgToken") {
|
|
# Print ending '}' for the group we just finished reading
|
|
$tex_mode_string .= '}';
|
|
}
|
|
|
|
# If there could be optional arguments next, copy them
|
|
while ($curr_args =~ s/^o// && $fileobject->lookAheadToken eq '[') {
|
|
my $opt = $fileobject->eatOptionalArgument;
|
|
$tex_mode_string .= $opt->exact_print;
|
|
}
|
|
$curr_args =~ s/^o*//; # Some OptArgs may not have appeared
|
|
|
|
if ($type eq "BegArgsToken" or $type eq "ArgToken") {
|
|
# Print beginning '{' for the group we're about to read
|
|
$tex_mode_string .= '{';
|
|
}
|
|
|
|
# Now copy the next required argument, if any
|
|
# Copy it verbatim (r), or translate it as regular LaTeX (R)?
|
|
if ($curr_args =~ s/^r//) {
|
|
my $group = $fileobject->eatRequiredArgument;
|
|
my $temp = $group->exact_print;
|
|
# Remove braces. They're put in explicitly
|
|
$temp =~ s/\{(.*)\}/$1/; # .* is greedy
|
|
$tex_mode_string .= $temp;
|
|
|
|
} elsif ($curr_args =~ s/^R//) {
|
|
print "\n" if $debug_on;
|
|
&print_tex_mode($tex_mode_string);
|
|
$tex_mode_string = "";
|
|
print "\nTranslating this argument for ",$temp_start->print,
|
|
" as regular LaTeX" if $debug_on;
|
|
|
|
} else { # anything but '' is weird
|
|
warn "weird arg $curr_args to ",$temp_start->print,"\n" if $curr_args;
|
|
}
|
|
|
|
# Finished tex mode copying
|
|
if ($type eq "Token" or $type eq "EndArgsToken") {
|
|
|
|
# Add {} to plain tokens followed by { or }. Otherwise {\foo}bar
|
|
# and \foo{bar} yield \foobar in the LaTeX files created by LyX
|
|
my $dummy;
|
|
if ($type eq "Token" and
|
|
$dummy=$fileobject->lookAheadToken and
|
|
$dummy =~ /[{}]/)
|
|
{
|
|
$tex_mode_string .= '{}';
|
|
}
|
|
|
|
# Print out the string
|
|
print "\n" if $debug_on;
|
|
&print_tex_mode($tex_mode_string);
|
|
$tex_mode_string = "";
|
|
|
|
# We're done with this token
|
|
pop(@tex_mode_tokens);
|
|
|
|
my $i = $type eq "Token" ? "" : " and its arguments";
|
|
my $j = $temp_start->print;
|
|
print "\nDone copying untranslatable token '$j'$i in TeX mode"
|
|
if $debug_on;
|
|
} # end tex copying?
|
|
} # end sub copy_latex_known
|
|
|
|
# Print a string in LyX "TeX mode"
|
|
# The goal of this subroutine is to create a block of LyX which will be
|
|
# translated exactly back to the original when LyX creates its temporary LaTeX
|
|
# file prior to creating a dvi file.
|
|
# Don't print \n\n at the end of the string... instead just set the new
|
|
# paragraph flag. Also, don't print whitespace at the beginning of the string.
|
|
# Print nothing if it's the beginning of a paragraph, or space otherwise.
|
|
# These two things avoid extra C-Enter's in the LyX file
|
|
sub print_tex_mode {
|
|
my $outstr = shift;
|
|
|
|
print "'$outstr'" if $debug_on;
|
|
|
|
# Handle extra \n's (& spaces) at beginning & end of string
|
|
my $str_ends_par = ($outstr =~ s/\n{2,}$//);
|
|
if ($IsNewParagraph) {
|
|
$outstr =~ s/^\s+//; # .e.g, $outstr follows '\begin{quote}'
|
|
} else {
|
|
# Any whitespace is equivalent to one space in LaTeX
|
|
$outstr =~ s/^\s+/ /; # e.g. $outstr follows "\LaTeX{}" or "Hi{}"
|
|
}
|
|
|
|
# Check whether string came right at the beginning of a new paragraph
|
|
# This *might* not be necessary. Probably can't hurt.
|
|
&CheckForNewParagraph;
|
|
|
|
# Get into TeX mode
|
|
print OUTFILE "$start_tex_mode";
|
|
|
|
# Do TeX mode translation;
|
|
$outstr =~ s/\\/\n\\backslash /g;
|
|
# don't translate \n in '\n\backslash' that we just made!
|
|
$outstr =~ s/\n(?!\\backslash)/\n\\newline \n/g;
|
|
|
|
# Print the actual token + arguments if any
|
|
print OUTFILE $outstr;
|
|
|
|
# Get OUT of LaTeX mode (and end nesting if nec.)
|
|
print OUTFILE "$end_tex_mode";
|
|
$IsNewParagraph = $str_ends_par;
|
|
|
|
return;
|
|
} # end sub print_tex_mode
|
|
|
|
############################ LAYOUT SUBROUTINES ###########################
|
|
|
|
sub CheckForNewParagraph {
|
|
# This subroutine makes sure we only write \layout command once per paragraph
|
|
# It's mostly necessary cuz 'Standard' layout is the default in LaTeX;
|
|
# there is no command which officially starts a standard environment
|
|
# If we're in a table, new layouts aren't allowed, so just return
|
|
# If $IsNewParagraph is 0, it does nothing
|
|
# If $INP==1, It starts a new paragraph
|
|
# If $CurrentAlignment is set, it prints the alignment command for this par.
|
|
# If $MayBeDeeper==1 and we're currently within a list environment,
|
|
# it starts a "deeper" Standard paragraph
|
|
my $dummy;
|
|
my $layout = $$CurrentLayoutStack[-1];
|
|
|
|
return if &RelyxTable::in_table;
|
|
|
|
if ($IsNewParagraph) {
|
|
# Handle standard text within a list environment specially
|
|
if ($MayBeDeeper) {
|
|
if ($layout =~ /^$ListLayouts$/o) {
|
|
push (@$CurrentLayoutStack, "Standard");
|
|
print "\nCurrent Layout Stack: @$CurrentLayoutStack\n"
|
|
if $debug_on;
|
|
print OUTFILE "\n\\begin_deeper ";
|
|
$layout = "Standard";
|
|
}
|
|
$MayBeDeeper = 0; # Don't test again until new paragraph
|
|
}
|
|
|
|
# Print layout command itself
|
|
print OUTFILE "\n\\layout $layout\n\n";
|
|
print OUTFILE $CurrentAlignment if $CurrentAlignment;
|
|
|
|
# Now that we've written the command, it's no longer a new paragraph
|
|
$IsNewParagraph = 0;
|
|
|
|
# And we're no longer waiting to see if this paragraph is empty
|
|
$PendingLayout = 0;
|
|
|
|
# When you write a new paragraph, reprint any font commands
|
|
foreach $dummy (keys %$CurrentFontStatus) {
|
|
my $currf = $CurrentFontStatus->{$dummy}->[-1];
|
|
if ($currf ne "default") {
|
|
print OUTFILE "\n$dummy $currf \n";
|
|
}
|
|
}
|
|
} # end if $INP
|
|
} # end sub CheckForNewParagraph
|
|
|
|
sub ConvertToLayout {
|
|
# This subroutine begins a new layout, pushing onto the layout stack, nesting
|
|
# if necessary. It doesn't actually write the \layout command -- that's
|
|
# done by CheckForNewParagraph.
|
|
# The subroutine assumes that it's being passed an environment name or macro
|
|
# which is known and which creates a known layout
|
|
# It uses the ToLayout hash (created by the ReadCommands module) which
|
|
# gives the LyX layout for a given LaTeX command or environment
|
|
# Arg0 is the environment or macro
|
|
my $name = shift;
|
|
|
|
my $layoutref = $ReadCommands::ToLayout->{$name};
|
|
my $layout = $layoutref->{'layout'};
|
|
my $keepempty = $layoutref->{'keepempty'};
|
|
my $dummy = ($name =~ /^\\/ ? "macro" : "environment");
|
|
print "\nChanging $dummy $name to layout $layout" if $debug_on;
|
|
|
|
# Nest if the layout stack has more than just "Standard" in it
|
|
if ($#{$CurrentLayoutStack} > 0) {
|
|
# Die here for sections & things that can't be nested!
|
|
print " Nesting!" if $debug_on;
|
|
print OUTFILE "\n\\begin_deeper ";
|
|
}
|
|
|
|
# If we still haven't printed the *previous* \layout command because that
|
|
# environment is empty, print it now! (This happens if an environment
|
|
# is nested inside a keepempty, like slide.)
|
|
&CheckForNewParagraph if $PendingLayout;
|
|
|
|
# Put the new layout onto the layout stack
|
|
push @$CurrentLayoutStack, $layout;
|
|
print "\nCurrent Layout Stack: @$CurrentLayoutStack" if $debug_on;
|
|
|
|
# Upcoming text will be new paragraph, needing a new layout cmd
|
|
$IsNewParagraph = 1;
|
|
|
|
# Test for nested "Standard" paragraph in upcoming text?
|
|
# Some environments can nest. Sections & Title commands can't
|
|
$MayBeDeeper = $layoutref->{"nestable"};
|
|
|
|
# We haven't yet read any printable stuff in the new paragraph
|
|
# If this is a layout that's allowed to be empty, prepare for an
|
|
# empty paragraph
|
|
$PendingLayout = $keepempty;
|
|
|
|
} # end sub ConvertToLayout
|
|
|
|
sub EndLayout {
|
|
# This subroutine ends a layout, popping the layout stack, etc.
|
|
# The subroutine assumes that it's being passed an environment name or macro
|
|
# which is known and which creates a known layout
|
|
# It uses the ToLayout hash (created by the ReadCommands module) which
|
|
# gives the LyX layout for a given LaTeX command or environment
|
|
# Arg0 is the environment or macro
|
|
my $name = shift;
|
|
|
|
my $layoutref = $ReadCommands::ToLayout->{$name};
|
|
my $layout = $layoutref->{'layout'};
|
|
my $dummy = ($name =~ /^\\/ ? "macro" : "environment");
|
|
print "\nEnding $dummy $name (layout $layout)" if $debug_on;
|
|
|
|
# If we still haven't printed the *previous* \layout command because that
|
|
# environment is empty, print it now! Before we pop the stack!
|
|
# This happens for a totally empty, non-nesting environment,
|
|
# like hollywood.sty's fadein
|
|
&CheckForNewParagraph if $PendingLayout;
|
|
|
|
my $mylayout = pop (@$CurrentLayoutStack);
|
|
|
|
# If a standard paragraph was the last thing in a list, then
|
|
# we need to end_deeper and then pop the actual list layout
|
|
# This should only happen if the Standard layout was nested
|
|
# in a nestable layout. We don't check.
|
|
if ($mylayout eq "Standard") {
|
|
print OUTFILE "\n\\end_deeper ";
|
|
print " End Standard Nesting!" if $debug_on;
|
|
$mylayout = pop (@$CurrentLayoutStack);
|
|
}
|
|
|
|
# The layout we popped off the stack had better match the
|
|
# environment (or macro) we're ending!
|
|
if ($mylayout ne $layout) { die "Problem with Layout Stack!\n"};
|
|
print "\nCurrent Layout Stack: @$CurrentLayoutStack" if $debug_on;
|
|
|
|
# If we're finishing a nested layout, we need to end_deeper
|
|
# This should only happen if the layout was nested
|
|
# in a nestable layout. We don't check.
|
|
# Note that if we're nested in a list environment and the
|
|
# NEXT paragraph is Standard, then we'll have an extra
|
|
# \end_deeper \begin_deeper in the LyX file. It's sloppy
|
|
# but it works, and LyX will get rid of it when it
|
|
# resaves the file.
|
|
if ($#{$CurrentLayoutStack} > 0) {
|
|
print " End Nesting!" if $debug_on;
|
|
print OUTFILE "\n\\end_deeper ";
|
|
}
|
|
|
|
# Upcoming text will be new paragraph, needing a new layout cmd
|
|
$IsNewParagraph = 1;
|
|
|
|
# Test for nested "Standard" paragraph in upcoming text?
|
|
# Some environments can nest. Sections & Title commands can't
|
|
$MayBeDeeper = $layoutref->{"nestable"};
|
|
} # end sub EndLayout
|
|
|
|
####################### MISCELLANEOUS SUBROUTINES ###########################
|
|
sub fixmath {
|
|
# Translate math commands LyX doesn't support into commands it does support
|
|
my $input = shift;
|
|
my $output = "";
|
|
|
|
while ($input =~ s/
|
|
(.*?) # non-token matter ($1)
|
|
(\\ # token ($2) is a backslash followed by ...
|
|
( ([^A-Za-z] \*?) | # non-letter (and *) ($4) OR
|
|
([A-Za-z]+ \*?) ) # letters (and *) ($5)
|
|
)//xs) # /x to allow whitespace/comments, /s to copy \n's
|
|
{
|
|
$output .= $1;
|
|
my $tok = $2;
|
|
if (exists $ReadCommands::math_trans{$tok}) {
|
|
$tok = $ReadCommands::math_trans{$tok};
|
|
# add ' ' in case we had, e.g., \|a, which would become \Verta
|
|
# Only need to do it in those special cases
|
|
$tok .= ' ' if
|
|
defined $4 && $tok =~ /[A-Za-z]$/ && $input =~ /^[A-Za-z]/;
|
|
}
|
|
$output .= $tok;
|
|
}
|
|
$output .= $input; # copy what's left in $input
|
|
|
|
return $output;
|
|
}
|
|
|
|
1; # return true to calling subroutine
|
|
|