]> git.lyx.org Git - lyx.git/blob - lib/reLyX/BasicLyX.pm
Hmmm. It appears I've been careless and already committed a work in progress
[lyx.git] / lib / reLyX / BasicLyX.pm
1 # This file is part of reLyX
2 # Copyright (c) 1998-9 Amir Karger karger@post.harvard.edu
3 # You are free to use and modify this code under the terms of
4 # the GNU General Public Licence version 2 or later.
5
6 package BasicLyX;
7 # This package includes subroutines to translate "clean" LaTeX to LyX.
8 # It translates only "basic" stuff, which means it doesn't do class-specific
9 #     things. (It uses the TeX parser Text::TeX)
10 use strict;
11
12 use RelyxTable; # Handle LaTeX tables
13 use RelyxFigure; # Handle LaTeX figures
14 use Verbatim;   # Copy stuff verbatim
15
16 use vars qw($bibstyle_insert_string $bibstyle_file $Begin_Inset_Include);
17 $bibstyle_insert_string = "%%%%%Insert bibliographystyle file here!";
18 $bibstyle_file = "";
19 $Begin_Inset_Include = "\\begin_inset Include";
20
21 #################### PACKAGE-WIDE VARIABLES ###########################
22 my $debug_on; # is debugging on?
23
24 ######
25 #Next text starts a new paragraph?
26 # $INP = 0 for plain text not starting a new paragraph
27 # $INP = 1 for text starting a new paragraph, so that we write a new
28 #    \layout command and renew any font changes for the new paragraph
29 # Starts as 1 cuz if the first text in the document is plain text
30 #    (not, e.g., in a \title command) it will start a new paragraph
31 my $IsNewParagraph = 1;
32 my $OldINP; #Save $IsNewParagraph during footnotes
33
34 # Some layouts may have no actual text in them before the next layout
35 # (e.g. slides). Pending Layout is set when we read a command that puts us
36 # in a new layout. If we get some regular text to print out, set it to false.
37 # But if we get to another layout command, first print out the command to
38 # start the pending layout.
39 my $PendingLayout = 0;
40
41 # HACK to protect spaces within optional argument to \item
42 my $protect_spaces = 0;
43
44 # $MBD = 1 if we're in a list, but haven't seen an '\item' command
45 #    In that case, we may need to start a nested "Standard" paragraph
46 my $MayBeDeeper = 0;
47 my $OldMBD; #Save $MBD during footnotes -- this should very rarely be necessary!
48
49 # Stack to take care of environments like Enumerate, Quote
50 # We need a separate stack for footnotes, which have separate layouts etc.
51 # Therefore we use a reference to an array, not just an array
52 my @LayoutStack = ("Standard"); #default if everything else pops off
53 my $CurrentLayoutStack = \@LayoutStack;
54
55 # Status of various font commands
56 # Every font characteristic (family, series, etc.) needs a stack, because
57 #    there may be nested font commands, like \textsf{blah \texttt{blah} blah}
58 # CurrentFontStatus usually points to the main %FontStatus hash, but
59 #     when we're in a footnote, it will point to a temporary hash
60 my %FontStatus = (
61     '\emph' => ["default"],
62     '\family' => ["default"],
63     '\series' => ["default"],
64     '\shape' => ["default"],
65     '\bar' => ["default"],
66     '\size' => ["default"],
67     '\noun' => ["default"],
68 );
69 my $CurrentFontStatus = \%FontStatus;
70
71 # Currently aligning paragraphs right, left, or center?
72 my $CurrentAlignment = "";
73 my $OldAlignment; # Save $AS during footnotes
74
75 # Global variables for copying tex stuff
76 my $tex_mode_string; # string we accumulate tex mode stuff in
77 my @tex_mode_tokens; # stack of tokens which required calling copy_latex_known
78
79 # LyX strings to start and end TeX mode
80 my $start_tex_mode = "\n\\latex latex \n";
81 my $end_tex_mode = "\n\\latex default \n";
82
83 # String to write before each item
84 my $item_preface = "";
85
86 #############  INFORMATION ABOUT LATEX AND LYX   #############################
87 # LyX translations of LaTeX font commands
88 my %FontTransTable = (
89    # Font commands
90    '\emph' => "\n\\emph on \n",
91    '\underline' => "\n\\bar under \n",
92    '\underbar' => "\n\\bar under \n", # plain tex equivalent of underline?
93    '\textbf' => "\n\\series bold \n",
94    '\textmd' => "\n\\series medium \n",
95    '\textsf' => "\n\\family sans \n",
96    '\texttt' => "\n\\family typewriter \n",
97    '\textrm' => "\n\\family roman \n",
98    '\textsc' => "\n\\shape smallcaps \n",
99    '\textsl' => "\n\\shape slanted \n",
100    '\textit' => "\n\\shape italic \n",
101    '\textup' => "\n\\shape up \n",
102    '\noun' => "\n\\noun on \n", # LyX abstraction of smallcaps
103
104 # Font size commands
105    '\tiny' => "\n\\size tiny \n",
106    '\scriptsize' => "\n\\size scriptsize \n",
107    '\footnotesize' => "\n\\size footnotesize \n",
108    '\small' => "\n\\size small \n",
109    '\normalsize' => "\n\\size default \n",
110    '\large' => "\n\\size large \n",
111    '\Large' => "\n\\size Large \n",
112    '\LARGE' => "\n\\size LARGE \n",
113    '\huge' => "\n\\size huge \n",
114    '\Huge' => "\n\\size Huge \n",
115    # This doesn't work yet!
116    #'\textnormal' => "\n\\series medium \n\\family roman \n\\shape up \n",
117 );
118
119 # Things LyX implements as "Floats"
120 my %FloatTransTable = (
121    # Footnote, Margin note
122    '\footnote' => "\n\\begin_float footnote \n",
123    '\thanks' => "\n\\begin_float footnote \n", # thanks is same as footnote
124    '\marginpar' => "\n\\begin_float margin \n",
125 );
126 # Environments that LyX implements as "floats"
127 my %FloatEnvTransTable = (
128     "table" => "\n\\begin_float tab \n",
129     "table*" => "\n\\begin_float wide-tab \n",
130     "figure" => "\n\\begin_float fig \n",
131     "figure*" => "\n\\begin_float wide-fig \n",
132 );
133
134 # Simple LaTeX tokens which are turned into small pieces of LyX text
135 my %TextTokenTransTable = (
136     # LaTeX escaped characters
137     '\_' => '_',
138     '\%' => '%',
139     '\$' => '$',
140     '\&' => '&',
141     '\{' => '{',
142     '\}' => '}',
143     '\#' => '#',
144     '\~' => '~',
145     '\^' => '^',
146     # \i and \j are used in accents. LyX doesn't recognize them in plain
147     #    text. Hopefully, this isn't a problem.
148     '\i' => '\i',
149     '\j' => '\j',
150
151     # Misc simple LaTeX tokens
152     '~'      => "\n\\protected_separator \n",
153     '@'      => "@", # TeX.pm considers this a token, but it's not really
154     '\@'     => "\\SpecialChar \\@",
155     '\ldots' => "\n\\SpecialChar \\ldots\{\}\n",
156     '\-'     => "\\SpecialChar \\-\n",
157     '\LaTeX' => "LaTeX",
158     '\LaTeXe' => "LaTeX2e",
159     '\TeX'    => "TeX",
160     '\LyX'    => "LyX",
161     '\lyxarrow' => "\\SpecialChar \\menuseparator\n",
162     '\hfill'  => "\n\\hfill \n",
163     '\noindent'        => "\n\\noindent \n",
164     '\textbackslash'   => "\n\\backslash \n",
165     '\textgreater'     => ">",
166     '\textless'        => "<",
167     '\textbar'         => "|",
168     '\textasciitilde'  => "~",
169 );
170
171 # LyX translations of some plain LaTeX text (TeX parser won't recognize it
172 #     as a Token, so we need to translate the Text::TeX::Text token.)
173 my %TextTransTable = (
174     # Double quotes
175     "``" => "\n\\begin_inset Quotes eld\n\\end_inset \n\n",
176     "''" => "\n\\begin_inset Quotes erd\n\\end_inset \n\n",
177
178     # Tokens that don't start with a backslash, so parser won't recognize them
179     # (LyX doesn't support them, so we just print them in TeX mode)
180     '?`' => "$start_tex_mode?`$end_tex_mode",
181     '!`' => "$start_tex_mode!`$end_tex_mode",
182 );
183
184 # Things that LyX translates as "LatexCommand"s
185 # E.g., \ref{foo} ->'\begin_inset LatexCommand \ref{foo}\n\n\end_inset \n'
186 # (Some take arguments, others don't)
187 my @LatexCommands = map {"\\$_"} qw(ref pageref label cite bibliography
188                                  index printindex tableofcontents
189                                  listofalgorithms listoftables listoffigures);
190 my @IncludeCommands = map {"\\$_"} qw(input include);
191 # Included postscript files
192 # LyX 1.0 can't do \includegraphics*!
193 my @GraphicsCommands = map {"\\$_"} qw(epsf epsffile epsfbox
194                                        psfig epsfig includegraphics);
195
196 # Accents. Most of these take an argument -- the thing to accent
197 # (\l and \L are handled as InsetLatexAccents, so they go here too)
198 my $AccentTokens = "\\\\[`'^#~=.bcdHklLrtuv\"]";
199
200 # Environments which describe justifying (converted to LyX \align commands)
201 #    and the corresponding LyX commands
202 my %AlignEnvironments = (
203     "center" => "\n\\align center \n",
204     "flushright" => "\n\\align right \n",
205     "flushleft" => "\n\\align left \n",
206 );
207
208 # Some environments' begin commands take an extra argument
209 # Print string followed by arg for each item in the list, or ignore the arg ("")
210 my %ExtraArgEnvironments = (
211     "thebibliography" => "",
212     "lyxlist" =>'\labelwidthstring ',
213     "labeling" =>'\labelwidthstring ', # koma script list
214 );
215
216 # Math environments are copied verbatim
217 my $MathEnvironments = "(math|displaymath|xxalignat|(equation|eqnarray|align|alignat|xalignat|multline|gather)(\\*)?)";
218 # ListLayouts may have standard paragraphs nested inside them.
219 my $ListLayouts = "Itemize|Enumerate|Description";
220
221 # Striaght translation of LaTeX lengths to LyX ones.
222 my %lengthAsLyXString = ('\textwidth' => 'text%',
223                          '\columnwidth' => 'col%',
224                          '\paperwidth' => 'page%',
225                          '\linewidth' => 'line%',
226                          '\paperheight' => 'pheight%',
227                          '\textheight' => 'theight%');
228
229 # passed a string and an array
230 # returns true if the string is an element of the array.
231 sub foundIn {
232     my $name = shift;
233     return grep {$_ eq $name} @_;
234 }
235
236 my @NatbibCommands = map {"\\$_"} qw(citet citealt citep citealp citeauthor);
237
238 # passed a string.
239 # returns true if it is a valid natbib citation
240 sub isNatbibCitation {
241     my $name = shift;
242
243     # These two have a single form
244     return 1 if ($name eq '\citeyear' or $name eq '\citeyearpar');
245
246     # Natbib citations can start with a 'C' or a 'c'
247     $name =~ s/^\\C/\\c/;
248     # The can end with a '*'
249     $name =~ s/\*$//;
250     # Is this doctored string found in the list of valid commands?
251     return foundIn($name, @NatbibCommands);
252     
253 }
254
255 #####################   PARSER INVOCATION   ##################################
256 sub call_parser {
257 # This subroutine calls the TeX parser & translator
258 # Before it does that, it does lots of setup work to get ready for parsing.
259 # Arg0 is the file to read (clean) LaTeX from
260 # Arg1 is the file to write LyX to
261 # Arg2 is the file to read layouts from (e.g., in LYX_DIR/layouts/foo.layout)
262
263     my ($InFileName, $OutFileName) = (shift,shift);
264
265 # Before anything else, set the package-wide variables based on the
266 #    user-given flags
267     # opt_d is set to 1 if '-d' option given, else (probably) undefined
268     $debug_on = (defined($main::opt_d) && $main::opt_d);
269
270     # Hash of tokens passed to the TeX parser
271     # Many values are $Text::TeX::Tokens{'\operatorname'}, which has
272     #    Type=>report_args and count=>1
273     # Note that we don't have to bother putting in tokens which will be simply
274     #    translated (e.g., from %TextTokenTransTable).
275     my %MyTokens = ( 
276         '{' => $Text::TeX::Tokens{'{'},
277         '}' => $Text::TeX::Tokens{'}'},
278         '\begin' => $Text::TeX::Tokens{'\begin'},
279         '\end' => $Text::TeX::Tokens{'\end'},
280
281         # Lots of other commands will be made by ReadCommands:Merge
282         # by reading the LaTeX syntax file
283
284         # Font sizing commands (local)
285         '\tiny' => {Type => 'local'},
286         '\small' => {Type => 'local'},
287         '\scriptsize' => {Type => 'local'},
288         '\footnotesize' => {Type => 'local'},
289         '\small' => {Type => 'local'},
290         '\normalsize' => {Type => 'local'},
291         '\large' => {Type => 'local'},
292         '\Large' => {Type => 'local'},
293         '\LARGE' => {Type => 'local'},
294         '\huge' => {Type => 'local'},
295         '\Huge' => {Type => 'local'},
296
297         # Tokens to ignore (which make a new paragraph)
298         # Just pretend they actually ARE new paragraph markers!
299         '\maketitle' => {'class' => 'Text::TeX::Paragraph'},
300     );
301     
302     # Now add to MyTokens all of the commands that were read from the
303     #    commands file by package ReadCommands
304     &ReadCommands::Merge(\%MyTokens);
305
306 # Here's the actual subroutine. The above is all preparation
307     # Output LyX file
308     my $zzz = $debug_on ? " ($InFileName --> $OutFileName)\n" : "... ";
309     print STDERR "Translating$zzz";
310     open (OUTFILE,">$OutFileName");
311
312     # Open the file to turn into LyX.
313     my $infile = new Text::TeX::OpenFile $InFileName,
314         'defaultact' => \&basic_lyx,
315         'tokens' => \%MyTokens;
316
317     # Process what's left of the file (everything from \begin{document})
318     $infile->process;
319
320     # Last line of the LyX file
321     print OUTFILE "\n\\the_end\n";
322     close OUTFILE;
323     #warn "Done with basic translation\n";
324     return;
325 } # end subroutine call_parser
326
327 # This is used as a toggle so that we know what to do when basic_lyx is
328 # passed a '$' or '$$' token.
329 my $inside_math=0;
330
331 sub starting_math {
332     my $name = shift;
333
334     if ($name eq '\(' || $name eq '\[' ||
335         # These tokens bound both ends of a math environment so we must check
336         # $inside_math to know what action to take.
337         ($name eq '$' || $name eq '$$') && !$inside_math) {
338
339         $inside_math = 1;
340         return 1;
341     }
342
343     # All other tokens
344     return 0;
345 }
346
347 sub ending_math {
348     my $name = shift;
349
350     if ($name eq '\)' || $name eq '\]' ||
351         # These tokens bound both ends of a math environment so we must check
352         # $inside_math to know what action to take.
353         ($name eq '$' || $name eq '$$') && $inside_math) {
354
355         $inside_math = 0;
356         return 1;
357     }
358
359     # All other tokens
360     return 0;
361 }
362
363 ##########################   MAIN TRANSLATOR SUBROUTINE   #####################
364 sub basic_lyx {
365 # This subroutine is called by Text::TeX::process each time subroutine
366 #     eat returns a value.
367 # Argument 0 is the return value from subroutine eat
368 # Argument 1 is the Text::TeX::OpenFile (namely, $TeXfile)
369     my $eaten = shift;
370     my $fileobject = shift;
371
372     # This handles most but maybe not all comments
373     # THere shouldn't be any if we've run CleanTeX.pl
374     print "Comment: ",$eaten->comment if defined $eaten->comment && $debug_on;
375
376     my $type = ref($eaten);
377     print "$type " if $debug_on;
378
379     # This loop is basically just a switch. However, making it a for
380     #    (1) makes $_ = $type (convenient)
381     #    (2) allows us to use things like next and last
382     TYPESW: for ($type) {
383
384         # some pre-case work
385         s/^Text::TeX:://o or die "unknown token?!";
386         my ($dummy, $tok);
387         my ($thistable);
388
389         # The parser puts whitespace before certain kinds of tokens along
390         # with that token. If that happens, save a space
391         my $pre_space = ""; # whitespace before a token
392         if (/BegArgsToken|^Token|::Group$/) {
393             $dummy = $eaten->exact_print;
394             # Only set prespace if we match something
395             #    We wouldn't want it to be more than one space, since that's
396             # illegal in LyX. Also, replace \t or \n with ' ' since they are
397             # ignored in LyX. Hopefully, this won't lead to anything worse
398             # than some lines with >80 chars
399             #    Finally, don't put a space at the beginning of a new paragraph
400             if (($dummy =~ /^\s+/) && !$IsNewParagraph) {
401                 $pre_space = " ";
402             }
403         }
404
405         # Handle blank lines.
406         if (m/^Paragraph$/o) {
407             # $INP <>0 means We will need a new layout command
408             $IsNewParagraph = 1;
409
410             # $MBD means start a begin_deeper within list environments
411             #    unless there's an \item command
412             $MayBeDeeper = 1;
413
414             last TYPESW;
415         }
416
417         # If, e.g., there's just a comment in this token, don't do anything
418         # This actually shouldn't happen if CleanTeX has already removed them
419         last TYPESW if !defined $eaten->print;
420         
421         # Handle LaTeX tokens
422         if (/^Token$/o) {
423
424             my $name = $eaten->token_name; # name of the token, e.g., "\large"
425             print "'$name' " if $debug_on;
426
427             # Tokens which turn into a bit of LyX text
428             if (exists $TextTokenTransTable{$name}) {
429                 &CheckForNewParagraph; #Start new paragraph if necessary
430
431                 my $to_print = $TextTokenTransTable{$name};
432
433                 # \@ has to be specially handled, because it depends on
434                 # the character AFTER the \@
435                 if ($name eq '\@') {
436                     my $next = $fileobject->eatGroup(1);
437                     my $ch="";
438                     $ch = $next->print or warn "\@ confused me!\n";
439                     if ($ch eq '.') {
440                         # Note: \@ CAN'T have pre_space before it
441                         print OUTFILE "$to_print$ch\n";
442                         print "followed by $ch" if $debug_on;
443                     } else {
444                        warn "LyX (or LaTeX) can't handle '$ch' after $name\n";
445                         print OUTFILE $ch;
446                     }
447
448                 } else { # it's not \@
449                     # Print the translated text (include preceding whitespace)
450                     print OUTFILE "$pre_space$to_print";
451                 } # end special handling for \@
452
453             # Handle tokens that LyX translates as a "LatexCommand" inset
454             } elsif (foundIn($name, @LatexCommands) ||
455                      isNatbibCitation($name)){
456                 &CheckForNewParagraph; #Start new paragraph if necessary
457                 print OUTFILE "$pre_space\n\\begin_inset LatexCommand ",
458                                $name,
459                               "\n\n\\end_inset \n\n";
460
461             # Math -- copy verbatim until you're done
462             } elsif (starting_math($name)) {
463                 print "\nCopying math beginning with '$name'\n" if $debug_on;
464                 # copy everything until end text
465                 $dummy = &Verbatim::copy_verbatim($fileobject, $eaten);
466                 $dummy = &fixmath($dummy); # convert '\sp' to '^', etc.
467
468                 &CheckForNewParagraph; # math could be first thing in a par
469                 print OUTFILE "$pre_space\n\\begin_inset Formula $name ";
470                 print $dummy if $debug_on;
471                 print OUTFILE $dummy;
472
473             } elsif (ending_math($name)) {
474                 # end math
475                 print OUTFILE "$name\n\\end_inset \n\n";
476                 print "\nDone copying math ending with '$name'" if $debug_on;
477
478             # Items in list environments
479             } elsif ($name eq '\item') {
480                 
481                 # What if we had a nested "Standard" paragraph?
482                 # Then write \end_deeper to finish the standard layout
483                 #     before we write the new \layout ListEnv command
484                 if ($$CurrentLayoutStack[-1] eq "Standard") {
485                     pop (@$CurrentLayoutStack); # take "Standard" off the stack
486                     print OUTFILE "\n\\end_deeper ";
487                     print "\nCurrent Layout Stack: @$CurrentLayoutStack"
488                           if $debug_on;
489                 } # end deeper if
490
491                 # Upcoming text (the item) will be a new paragraph, 
492                 #    requiring a new layout command based on whichever
493                 #    kind of list environment we're in
494                 $IsNewParagraph = 1;
495
496                 # But since we had an \item command, DON'T nest a
497                 #    deeper "Standard" paragraph in the list
498                 $MayBeDeeper = 0;
499
500                 # Check for an optional argument to \item
501                 # If so, within the [] we need to protect spaces
502                 # TODO: In fact, for description, if there's no [] or
503                 # there's an empty [], then we need to write a ~, since LyX
504                 # will otherwise make the next word the label
505                 # If it's NOT a description & has a [] then we're stuck!
506                 # They need to fix the bullets w/in lyx!
507                 if (($dummy = $fileobject->lookAheadToken) &&
508                     ($dummy =~ /\s*\[/)) {
509                     $fileobject->eatFixedString('\['); # eat the [
510                     $protect_spaces = 1;
511                 }
512
513                 # Special lists (e.g. List layout) have to print something
514                 # before each item. In that case, CFNP and print it
515                 if ($item_preface) {
516                     &CheckForNewParagraph;
517                     print OUTFILE $item_preface;
518                 }
519
520             # Font sizing commands
521             # (Other font commands are TT::BegArgsTokens because they take
522             #     arguments. Font sizing commands are 'local' TT::Tokens)
523             } elsif (exists $FontTransTable{$name}) {
524                 my $command = $FontTransTable{$name}; #e.g., '\size large'
525
526                 if (! $IsNewParagraph) {
527                     print OUTFILE "$pre_space$command";
528                 } #otherwise, wait until we've printed the new paragraph command
529
530                 # Store the current font change
531                 ($dummy = $command) =~ s/\s*(\S+)\s+(\w+)\s*/$1/;
532                 die "Font command error" if !exists $$CurrentFontStatus{$dummy};
533                 push (@{$CurrentFontStatus->{$dummy}}, $2);
534                 print "\nCurrent $dummy Stack: @{$CurrentFontStatus->{$dummy}}"
535                       if $debug_on;
536
537             # Table stuff
538             } elsif ($name eq '&') {
539                 if ($thistable = &RelyxTable::in_table) {
540                     print OUTFILE "\n\\newline \n";
541                     $thistable->nextcol;
542                 } else {warn "& is illegal outside a table!"}
543
544             } elsif ($name eq '\\\\' || $name eq '\\newline' || $name eq "\\tabularnewline") {
545                 &CheckForNewParagraph; # could be at beginning of par?
546                 print OUTFILE "\n\\newline \n";
547
548                 # If we're in a table, \\ means add a row to the table
549                 # Note: if we're on the last row of the table, this extra
550                 # row will get deleted later. This hack is necessary, because
551                 # we don't know while reading when the last row is!
552                 if ($thistable = &RelyxTable::in_table) {
553                     $thistable->addrow;
554                 }
555
556             } elsif ($name eq '\hline') {
557                 if ($thistable = &RelyxTable::in_table) {
558                     # hcline does hline if no arg is given
559                     $thistable->hcline;
560                 } else {warn "\\hline is illegal outside a table!"}
561
562             # Figures
563
564             } elsif ($name =~ /^\\epsf[xy]size$/) {
565                 # We need to eat '=' followed by EITHER more text OR
566                 # one (or more?!) macros describing a TeX size
567                 my $arg = $fileobject->eatMultiToken;
568                 my $length = $arg->print;
569                 $length =~ s/^\s*=\s*// or warn "weird '$name' command!";
570
571                 # If there's no "cm" or other letters in $length, the next token
572                 # ought to be something like \textwidth. Then it will be empty
573                 # or just have numbers in it.
574                 # This is bugprone. Hopefully not too many people use epsf!
575                 if ($length =~ /^[\d.]*\s*$/) {
576                     my $next = $fileobject->eatMultiToken;
577                     $length .= $next->print;
578                 }
579                 $length =~ s/\s*$//; # may have \n at end
580
581                 # If we can't parse the command, print it in tex mode
582                 &RelyxFigure::parse_epsfsize($name, $length) or 
583                     &print_tex_mode("$name=$length");
584
585             # Miscellaneous...
586
587             } elsif ($name =~ /\\verb.*?/) {
588                 my $dummy = &Verbatim::copy_verb($fileobject, $eaten);
589                 print "\nCopying $name in TeX mode: " if $debug_on;
590                 &print_tex_mode ($dummy);
591
592             # Otherwise it's an unknown token, which must be copied
593             #     in TeX mode, along with its arguments, if any
594             } else {
595                 if (defined($eaten->relyx_args($fileobject))) {
596                    &copy_latex_known($eaten, $fileobject);
597                 } else { # it's not in MyTokens
598                     &copy_latex_unknown($eaten, $fileobject);
599                 }
600             }
601
602             last TYPESW;
603         }
604         
605         # Handle tokens that take arguments, like \section{},\section*{}
606         if (/^BegArgsToken$/) {
607             my $name = $eaten->token_name;
608             print "$name" if $debug_on;
609
610             # Handle things that LyX translates as a "LatexCommand" inset
611             if (foundIn($name, @LatexCommands) || isNatbibCitation($name)){
612                 &CheckForNewParagraph; #Start new paragraph if necessary
613
614                 print OUTFILE "$pre_space\n\\begin_inset LatexCommand ";
615
616                 #    \bibliography gets handled as a LatexCommand inset, but
617                 # it's a special case, cuz LyX syntax expects "\BibTeX"
618                 # instead of "\bibliography" (I have no idea why), and because
619                 # we have to print extra stuff
620                 #    Because we might not have encountered the
621                 # \bibliographystyle command yet, we write
622                 # "insert bibstyle here", and replace that string
623                 # with the actual bibliographystyle argument in
624                 # LastLyX (i.e., the next pass through the file)
625                 if ($name eq "\\bibliography") {
626                     print OUTFILE "\\BibTeX[", $bibstyle_insert_string, "]";
627                 } else {
628                     print OUTFILE "$name";
629                 }
630
631                 # \cite takes an optional argument, e.g.
632                 my $args = $eaten->relyx_args ($fileobject);
633                 while ($args =~ s/^o//) {
634                     my $tok = $fileobject->eatOptionalArgument;
635                     my $dummy = $tok->exact_print;
636                     print OUTFILE $dummy;
637                 }
638
639                 print OUTFILE "\{";
640                 last TYPESW; # skip to the end of the switch
641             }
642
643             if (grep {$_ eq $name} @IncludeCommands) {
644                 &CheckForNewParagraph; #Start new paragraph if necessary
645                 print OUTFILE "$pre_space\n$Begin_Inset_Include $name\{";
646                 last TYPESW; # skip to the end of the switch
647             }
648
649             # This is to handle cases where _ is used, say, in a filename.
650             # When _ is used in math mode, it'll be copied by the math mode
651             # copying subs. Here we handle cases where it's used in non-math.
652             # Examples are filenames for \include & citation labels.
653             # (It's illegal to use it in regular LaTeX text.)
654             if ($name eq "_") {
655                print OUTFILE $eaten->exact_print;
656                last TYPESW;
657             }
658
659             # Sectioning and Title environments (using a LyX \layout command)
660             if (exists $ReadCommands::ToLayout->{$name}) {
661                 &ConvertToLayout($name);
662                 last TYPESW; #done translating
663
664             # Font characteristics
665             } elsif (exists $FontTransTable{$name}) {
666                 my $dum2;
667                 my $command = $FontTransTable{$name};
668                 ($dummy, $dum2) = ($command =~ /(\S+)\s+(\w+)/);
669
670                 # HACK so that "\emph{hi \emph{bye}}" yields unemph'ed "bye"
671                 if ( ($dummy eq "\\emph") && 
672                      ($CurrentFontStatus->{$dummy}->[-1] eq "on")) {
673                        $dum2 = "default"; # "on" instead of default?
674                        $command =~ s/on/default/;
675                 }
676
677                 # If we're about to start a new paragraph, save writing
678                 #    this command until *after* we write '\layout Standard'
679                 if (! $IsNewParagraph) {
680                     print OUTFILE "$pre_space$command";
681                 }
682
683                 # Store the current font change
684                 die "Font command error" if !exists $$CurrentFontStatus{$dummy};
685                 push (@{$CurrentFontStatus->{$dummy}}, $dum2);
686
687
688             # Handle footnotes and margin notes
689             # Make a new font table & layout stack which will be local to the 
690             #    footnote or marginpar
691             } elsif (exists $FloatTransTable{$name}) {
692                 my $command = $FloatTransTable{$name};
693
694                 # Open the footnote
695                 print OUTFILE "$pre_space$command";
696
697                 # Make $CurrentFontStatus point to a new (anonymous) font table
698                 $CurrentFontStatus =  {
699                     '\emph' => ["default"],
700                     '\family' => ["default"],
701                     '\series' => ["default"],
702                     '\shape' => ["default"],
703                     '\bar' => ["default"],
704                     '\size' => ["default"],
705                     '\noun' => ["default"],
706                 };
707
708                 # And make $CurrentLayoutStack point to a new (anon.) stack
709                 $CurrentLayoutStack = ["Standard"];
710
711                 # Store whether we're at the end of a paragraph or not
712                 #    for when we get to end of footnote AND 
713                 # Note that the footnote text will be starting a new paragraph
714                 # Also store the current alignment (justification)
715                 $OldINP = $IsNewParagraph; $OldMBD = $MayBeDeeper;
716                 $OldAlignment = $CurrentAlignment;
717                 $IsNewParagraph = 1;
718                 $MayBeDeeper = 0; #can't be deeper at beginning of footnote
719                 $CurrentAlignment = "";
720
721             # Accents
722             } elsif ($name =~ m/^$AccentTokens$/) {
723                 &CheckForNewParagraph; # may be at the beginning of a par
724
725                 print OUTFILE "$pre_space\n",'\i ',$name,'{'
726             
727             # Included Postscript Figures
728             # Currently, all postscript including commands take one
729             # required argument and 0 to 2 optional args, so we can
730             # clump them together in one else.
731             } elsif (grep {$_ eq $name} @GraphicsCommands) {
732                 &CheckForNewParagraph; # may be at the beginning of a par
733                 my $arg1 = $fileobject->eatOptionalArgument;
734                 # arg2 is a token of an empty string for most commands
735                 my $arg2 = $fileobject->eatOptionalArgument;
736                 my $arg3 = $fileobject->eatRequiredArgument;
737                 my $save = $arg1->exact_print . $arg2->exact_print .
738                            $arg3->exact_print;
739
740                 # Parse and put figure into LyX file
741                 # Print it verbatim if we didn't parse correctly
742                 my $thisfig = new RelyxFigure::Figure;
743                 if ($thisfig->parse_pscommand($name, $arg1, $arg2, $arg3)) {
744                     print OUTFILE $thisfig->print_info;
745                 } else {
746                     &print_tex_mode($eaten->exact_print . $save);
747                 }
748
749             # Tables
750
751             } elsif ($name eq "\\multicolumn") {
752                 if ($thistable = &RelyxTable::in_table) {
753                     # the (simple text) first arg.
754                     $dummy = $fileobject->eatRequiredArgument->contents->print;
755                     my $group = $fileobject->eatRequiredArgument;
756                     $thistable->multicolumn($dummy, $group);
757                 } else {warn "\\multicolumn is illegal outside a table!"}
758
759             } elsif ($name eq '\cline') {
760                 if ($thistable = &RelyxTable::in_table) {
761                     # the (simple text) first arg.
762                     $dummy = $fileobject->eatRequiredArgument->contents->print;
763                     # sub hcline does cline if an arg is given
764                     $thistable->hcline($dummy);
765                 } else {warn "\\cline is illegal outside a table!"}
766
767             # Bibliography
768
769             } elsif ($name eq '\bibliographystyle') {
770                 $tok = $fileobject->eatRequiredArgument;
771                 $bibstyle_file = "";
772                 # There may be >1 token in the {}, e.g. "{a_b}" -> 3 tokens
773                 my @toks = $tok->contents;
774                 foreach $tok (@toks) {
775                     # kludge: CleanTeX adds {} after _
776                     $tok = $tok->contents if ref($tok) eq "Text::TeX::Group";
777                     $bibstyle_file .= $tok->print;
778                 }
779                 print "\nBibliography style file is $bibstyle_file"if $debug_on;
780
781             # LyX \bibitem actually looks just like LaTeX bibitem, except
782             # it's in a Bibliography par & there must be a space after the
783             # bibitem command. Note we need to explicitly print the braces...
784             } elsif ($name eq "\\bibitem") {
785                 $IsNewParagraph=1; # \bibitem always starts new par. in LyX
786                 &CheckForNewParagraph;
787
788                 $tok = $fileobject->eatOptionalArgument;
789                 print OUTFILE "$name ", $tok->exact_print, "{";
790
791             # Miscellaneous
792
793             # ensuremath -- copy verbatim until you're done
794             # but add \( and \)
795             # Note that we'll only get here if the command is NOT in math mode
796             } elsif ($name eq '\ensuremath') {
797                 print "\nCopying math beginning with '$name'\n" if $debug_on;
798                 my $tok = $fileobject->eatGroup; # eat math expression
799                 my $dummy = $tok->exact_print;
800                 $dummy =~ s/\{(.*)\}/$1/;
801                 $dummy = &fixmath($dummy); # convert '\sp' to '^', etc.
802
803                 &CheckForNewParagraph; # math could be first thing in a par
804                 print OUTFILE "$pre_space\n\\begin_inset Formula \\( ";
805                 print $dummy if $debug_on;
806                 print OUTFILE $dummy;
807
808                 # end math
809                 print OUTFILE "\\)\n\\end_inset \n\n";
810                 print "\nDone copying math" if $debug_on;
811
812             # Token in the ReadCommands command list that basic_lyx doesn't
813             #    know how to handle
814             } else {
815                 &copy_latex_known($eaten,$fileobject);
816             } # end big if
817
818             # Exit the switch
819             last TYPESW;
820         }
821
822         # ArgTokens appear when we've used eatRequiredArgument
823         if (/^ArgToken$/) {
824             # If we're copying a recognized but untranslatable token in tex mode
825             my $tok = $tex_mode_tokens[-1] || 0;
826             if ($eaten->base_token == $tok) {
827                 &copy_latex_known($eaten,$fileobject);
828             }
829         
830             last TYPESW;
831         }
832
833         if (/^EndArgsToken$/) {
834             # If we're copying a recognized but untranslatable token in tex mode
835             my $tok = $tex_mode_tokens[-1] || 0;
836             if ($eaten->base_token eq $tok) {
837                 &copy_latex_known($eaten,$fileobject);
838                 last TYPESW;
839             }
840
841             my $name = $eaten->token_name;
842             print "$name" if $debug_on;
843
844             # Handle things that LyX translates as a "LatexCommand" inset
845             # or "Include" insets
846             if (foundIn($name, @LatexCommands, @IncludeCommands) ||
847                 isNatbibCitation($name)){
848                 print OUTFILE "\}\n\n\\end_inset \n\n";
849
850             } elsif (exists $ReadCommands::ToLayout->{$name}) {
851                 &EndLayout($name);
852
853             # Font characteristics
854             # Pop the current FontStatus stack for a given characteristic
855             #    and give the new command (e.g., \emph default)
856             } elsif (exists $FontTransTable{$name}) {
857                 my $command = $FontTransTable{$name};
858                 ($dummy) = ($command =~ /(\S+)\s+\w+/);
859                 pop @{$CurrentFontStatus->{$dummy}};
860                 $command = "\n$dummy $CurrentFontStatus->{$dummy}->[-1] \n";
861                 print OUTFILE "$command";
862
863             # Footnotes and marginpars
864             } elsif (exists $FloatTransTable{$name}) {
865                 print OUTFILE "\n\\end_float \n\n";
866
867                 # Reset the layout stack and font status table pointers to
868                 #    point to the global stack/table again, instead of the
869                 #    footnote-specific stack/table
870                 $CurrentFontStatus = \%FontStatus;
871                 $CurrentLayoutStack = \@LayoutStack;
872
873                 # We need to reissue any font commands (but not layouts)
874                 foreach $dummy (keys %$CurrentFontStatus) {
875                     if ($CurrentFontStatus->{$dummy}->[-1] ne "default") {
876                         print OUTFILE $FontTransTable{$dummy};
877                     }
878                 }
879
880                 # Same paragraph status as we had before the footnote started
881                 $IsNewParagraph = $OldINP; $MayBeDeeper = $OldMBD;
882                 $CurrentAlignment = $OldAlignment;
883
884             } elsif ($name =~ m/^$AccentTokens$/) {
885                 print OUTFILE "}\n";
886             
887             } elsif ($name eq "\\bibitem") {
888                 print OUTFILE "}\n";
889             } # End if on $name
890
891             # Exit main switch
892             last TYPESW;
893         } # end if EndArgsToken
894
895         # Handle END of scope of local commands like \large
896         if (/^EndLocal$/) {
897             my $name = $eaten->token_name; #cmd we're ending, e.g.,\large
898             print $name if $debug_on;
899
900             if (exists $FontTransTable{$name}) {
901                 my $command = $FontTransTable{$name};
902                 ($dummy = $command) =~ s/\s*(\S*)\s+(\w+)\s*/$1/; #e.g., '\size'
903                 die "Font command error" if !exists $$CurrentFontStatus{$dummy};
904                 # TT::OF->check_presynthetic returns local commands FIFO!
905                 # So pop font stack, but warn if we pop the wrong thing
906                 warn " font confusion?" if
907                            pop @{$CurrentFontStatus->{$dummy}} ne $2;
908                 print "\nCurrent $dummy Stack: @{$CurrentFontStatus->{$dummy}}"
909                       if $debug_on;
910                 my $newfont = $CurrentFontStatus->{$dummy}->[-1];
911                 $command = "\n$dummy $newfont\n";
912                 print OUTFILE "$command";
913
914             } else {
915                 warn "Unknown EndLocal token!\n";
916             }
917
918             last TYPESW;
919         }
920
921         # We don't print { or }, but we make sure that the spacing is correct
922         # Handle '{'
923         if (/^Begin::Group$/) {
924             print OUTFILE "$pre_space";
925             last TYPESW;
926         }
927
928         # Handle '{'
929         if (/^End::Group$/) {
930             print OUTFILE "$pre_space";
931             last TYPESW;
932         }
933
934         # Handle \begin{foo}
935         if (/^Begin::Group::Args$/) {
936             print $eaten->print," " if $debug_on; # the string "\begin{foo}"
937             my $env = $eaten->environment;
938             
939             # Any environment found in the layouts files
940             if (exists $ReadCommands::ToLayout->{$env}) {
941                 &ConvertToLayout($env);
942
943                 # Some environments have an extra argument. In that case,
944                 # print the \layout command (cuz these environments always
945                 # start new pars). Then either print the extra arg or
946                 # ignore it (depending on the environment).
947                 if (exists $ExtraArgEnvironments{$env}) {
948                     # Should be just one token in the arg.
949                     my $arg = $fileobject->eatBalanced->contents->print;
950
951                     if ($ExtraArgEnvironments{$env}) { #print it
952                         print "\nArgument $arg to $env environment"
953                                                                 if $debug_on;
954                         $item_preface = $ExtraArgEnvironments{$env} . $arg."\n";
955
956                     } else { #ignore it
957                         print "\nIgnoring argument '$arg' to $env environment"
958                                                                 if $debug_on;
959                     }
960                 } # end if for reading extra args to \begin command
961
962             # Math environments
963             } elsif ($env =~ /^$MathEnvironments$/o) {
964                 &CheckForNewParagraph; # may be beginning of paragraph
965                 my $begin_text = $eaten->print;
966                 print "\nCopying math beginning with '$begin_text'\n"
967                                                               if $debug_on;
968                 print OUTFILE "\n\\begin_inset Formula $begin_text ";
969                 $dummy = &Verbatim::copy_verbatim($fileobject, $eaten);
970                 $dummy = &fixmath($dummy); # convert '\sp' to '^', etc.
971                 print $dummy if $debug_on;
972                 print OUTFILE $dummy;
973
974             # Alignment environments
975             } elsif (exists $AlignEnvironments{$env}) {
976                 # Set it to the command which creates this alignment
977                 $CurrentAlignment = $AlignEnvironments{$env};
978                 ($dummy) = ($CurrentAlignment =~ /\S+\s+(\w+)/);
979                 print "\nNow $dummy-aligning text " if $debug_on;
980
981                 # alignment environments automatically start a new paragraph
982                 $IsNewParagraph = 1;
983
984             # Environments lyx translates to floats
985             } elsif (exists $FloatEnvTransTable{$env}) {
986                 # this really only matters if it's at the very
987                 # beginning of the doc.
988                 &CheckForNewParagraph;
989
990                 $tok = $fileobject->eatOptionalArgument;
991                 if ($tok && defined ($dummy = $tok->print) && $dummy) {
992                     print "\nIgnoring float placement '$dummy'" if $debug_on;
993                 }
994                 my $command = $FloatEnvTransTable{$env};
995
996                 # Open the table/figure
997                 print OUTFILE "$command";
998
999             # table
1000             } elsif ($env =~ /^tabular$/) { # don't allow tabular* or ctabular
1001                 # Table must start a new paragraph
1002                 $IsNewParagraph = 1; $MayBeDeeper = 1;
1003                 # We want to print table stuff *after* a \layout Standard
1004                 &CheckForNewParagraph;
1005
1006                 # Since table info needs to come *before* the table content,
1007                 #    put a line in the output file saying that the *next*
1008                 #    reLyX pass needs to put the table info there
1009                 print OUTFILE "\n$RelyxTable::TableBeginString\n";
1010
1011                 # Read and ignore an optional argument [t] or [b]
1012                 $tok = $fileobject->eatOptionalArgument;
1013                 if ($tok && defined ($dummy = $tok->print) && $dummy) {
1014                     print "\nIgnoring positioning arg '$dummy'" if $debug_on;
1015                 }
1016
1017                 # Read the argument into a TT::Group
1018                 #   (that group may contain groups, e.g. for {clp{10cm}}
1019                 $tok = $fileobject->eatGroup;
1020                 new RelyxTable::Table $tok;
1021
1022             # minipage
1023             } elsif ($env eq "minipage") {
1024                 &CheckForNewParagraph;
1025
1026                 print OUTFILE "\\begin_inset Minipage\n";
1027
1028                 # The minipage environment is defined as:
1029                 # \begin{minipage}[pos][height][inner-pos]{width} <text>
1030                 # \end{minipage}
1031
1032                 # Read the position optional argument, if it exists
1033                 $tok = $fileobject->eatOptionalArgument;
1034                 my $pos = $tok->print if defined($tok->print);
1035
1036                 my %map = ('t' => '0', 'c' => '1', 'b' => '2');
1037                 if ($debug_on && $pos ne '' && !defined($map{$pos})) {
1038                     print "\nIgnoring unknown positioning arg '$pos'\n";
1039                 }
1040
1041                 # The minipage is centred by default.
1042                 $pos = '1' if (!defined($map{$pos}) ||
1043                                   ($pos = $map{$pos}) eq '');
1044
1045                 # Read the height optional argument, if it exists
1046                 my $height = '0pt';
1047                 $tok = $fileobject->eatOptionalArgument;
1048                 if (defined($tok->print)) {
1049                     $height = $tok->print;
1050                     # if something like '4.5\columnwidth', translate into
1051                     # LyXese.
1052                     if ($height =~ /([0-9.]*)\s*(\\[a-z]*)/) {
1053                         if (defined($lengthAsLyXString{$2})) {
1054                             $height = ($1 * 100) . $lengthAsLyXString{$2};
1055                         }
1056                     }
1057                 }
1058
1059                 # Read the inner-pos optional argument, if it exists
1060                 my $innerpos = $pos;
1061                 $tok = $fileobject->eatOptionalArgument;
1062                 if (defined($tok->print)) {
1063                     my $arg = $tok->print;
1064                     print("\nMinipage inner-pos argument, $arg, is ",
1065                           "currently ignored\n");
1066                 }
1067
1068                 # Read the width as (a reference to) an array of tokens.
1069                 $tok = $fileobject->eatBalanced;
1070                 # $width is Something like either '4.5cm' or '\columnwidth'.
1071                 my $width = pop(@{$tok})->print;
1072                 # If $width is something like '\columnwidth', then manipulate
1073                 # it into LyX format and also extract the length itself.
1074                 if (defined($lengthAsLyXString{$width})) {
1075                     $width = $lengthAsLyXString{$width};
1076                     my $val = pop(@{$tok});
1077                     $val = (defined($val)) ? $val->print : '0';
1078                     $width = ($val * 100) . $width;
1079                 }
1080
1081                 print OUTFILE "position $pos\n";
1082                 print OUTFILE "inner_position $innerpos\n";
1083                 print OUTFILE "height $height\n";
1084                 print OUTFILE "width $width\n";
1085                 print OUTFILE "collapsed false\n";
1086
1087             # \begin document
1088             } elsif ($env eq "document") {
1089                 # do nothing
1090                 #print "\nStarting to translate actual document" if $debug_on;
1091
1092             # Special environments to copy as regular text (-r option).
1093             # Do this by copying the \begin & \end command in TeX mode
1094             # (\Q\E around $env allows *'s in environment names!)
1095             } elsif (grep /^\Q$env\E$/, @ReadCommands::regular_env) {
1096                 print "\nCopying $env environment as regular text\n"
1097                                                               if $debug_on;
1098                 $dummy = $eaten->print; # \begin{env}, ignore initial whitespace
1099                 &print_tex_mode($dummy);
1100
1101             # otherwise, it's an unknown environment
1102             # In that case, copy everything up to the \end{env}
1103             #    Save stuff in global tex_mode_string so we can print it
1104             # when we read & handle the \end{env}
1105             } else {
1106
1107                 print "\nUnknown environment $env" if $debug_on;
1108                 $tex_mode_string = "";
1109                 # print "\begin{env}
1110                 # For reLyXskip env, don't print the \begin & \end commands!
1111                 $tex_mode_string .= $eaten->exact_print 
1112                                 unless $env eq "reLyXskip";
1113                 $tex_mode_string .=&Verbatim::copy_verbatim($fileobject,$eaten);
1114             }
1115
1116             last TYPESW;
1117         }
1118         
1119         # Handle \end{foo}
1120         if (/^End::Group::Args$/) {
1121             print $eaten->print," " if $debug_on; # the string "\end{foo}"
1122             my $env = $eaten->environment;
1123
1124             # End of list or quote/verse environment
1125             # OR special environment given with -t option
1126             if (exists $ReadCommands::ToLayout->{$env}) {
1127                 &EndLayout($env);
1128                 $item_preface = ""; # reset when at end of List env.
1129
1130             # End of math environments
1131             } elsif ($env =~ /^$MathEnvironments$/o) {
1132                 print OUTFILE "\\end{$env}\n\\end_inset \n\n";
1133                 print "\nDone copying math environment '$env'" if $debug_on;
1134
1135             } elsif (exists $AlignEnvironments{$env}) {
1136                 # Back to block alignment
1137                 $CurrentAlignment = "";
1138                 print "\nBack to block alignment" if $debug_on;
1139
1140                 # assume that \end should end a paragraph
1141                 # This isn't correct LaTeX, but LyX can't align part of a par
1142                 $IsNewParagraph = 1;
1143
1144             # Environments lyx translates to floats
1145             } elsif (exists $FloatEnvTransTable{$env}) {
1146                 print OUTFILE "\n\\end_float \n\n";
1147
1148             # table
1149             } elsif ($env =~ /tabular$/) { # don't allow tabular*
1150                 if ($thistable = &RelyxTable::in_table) {
1151                     $thistable->done_reading;
1152                     print OUTFILE "\n$RelyxTable::TableEndString\n";
1153                 } else {warn "found \\end{tabular} when not in table!"}
1154
1155                 # Anything after a table will be a new paragraph
1156                 $IsNewParagraph = 1; $MayBeDeeper = 1;
1157
1158             # minipage
1159             } elsif ($env eq "minipage") {
1160                 print OUTFILE "\n\\end_inset \n\n";
1161
1162                 # Next stuff will be new env.
1163                 # $IsNewParagraph = 1;
1164
1165             } elsif ($env eq "document") {
1166                 print "\nDone with document!" if $debug_on;
1167
1168             # "regular" environment given with -r option
1169             } elsif (grep /^\Q$env\E$/, @ReadCommands::regular_env) {
1170                 $dummy = $eaten->print; # \end{env}, ignore initial whitespace
1171                 &print_tex_mode($dummy);
1172
1173                 # Next stuff will be new env.
1174                 $IsNewParagraph = 1;
1175
1176             # End of unknown environments. We're already in TeX mode
1177             } else {
1178                 # Add \end{env} (including initial whitespace) to string
1179                 # For reLyXskip environment, don't print \begin & \end commands!
1180                 $tex_mode_string .= $eaten->exact_print
1181                                        unless $env eq "reLyXskip";
1182                 # Now print it
1183                 &print_tex_mode($tex_mode_string);
1184                 print "Done copying unknown environment '$env'" if $debug_on;
1185             }
1186
1187             last TYPESW;
1188
1189         }
1190
1191         # Note for text handling: we have to do lots of stuff to handle
1192         # spaces in (as close as possible to) the same way that LaTeX does
1193         #    LaTeX considers all whitespace to be the same, so basically, we
1194         # convert each clump of whitespace to one space. Unfortunately, there
1195         # are special cases, like whitespace at the beginning/end of a par,
1196         # which we have to get rid of to avoid extra spaces in the LyX display.
1197         #    \n at the end of a paragraph must be considered like a space,
1198         # because the next line might begin with a token like \LyX. But
1199         # if the next line starts with \begin, say, then an extra space will be
1200         # generated in the LyX file. Oh well. It doesn't affect the dvi file.
1201         if (/^Text$/) {
1202             my $outstr = $eaten->print; # the actual text
1203
1204             # don't bother printing whitespace
1205             #    Note: this avoids the problem of extra whitespace generating
1206             # extra Text::TeX::Paragraphs, which would generate extra
1207             # \layout commands
1208             last TYPESW if $outstr =~ /^\s+$/;
1209
1210             # whitespace at beginning of a paragraph is meaningless
1211             # e.g. \begin{foo}\n hello \end{foo} shouldn't print the \n
1212             # (Note: check IsNewParagraph BEFORE calling &CFNP, which zeros it)
1213             my $replace = $IsNewParagraph ? "" : " ";
1214             $outstr =~ s/^\s+/$replace/;
1215
1216             # Only write '\layout Standard' once per paragraph
1217             &CheckForNewParagraph;
1218
1219             # \n\n signals end of paragraph, so get rid of it (and any space
1220             # before it)
1221             $outstr =~ s/\s*\n\n$//;
1222
1223             # Print the LaTeX text to STDOUT
1224             print "'$outstr'" if $debug_on;
1225
1226             # LyX *ignores* \n and \t, whereas LaTeX considers them just
1227             # like a space.
1228             #    Also, many spaces are equivalent to one space in LaTeX
1229             # (But LyX misleadingly displays them on screen, so get rid of them)
1230             $outstr =~ s/\s+/ /g;
1231
1232             # protect spaces in an optional argument if necessary
1233             # Put a SPACE after the argument for List, Description layouts
1234             if ($protect_spaces) {
1235                 $dummy = $TextTokenTransTable{'~'};
1236
1237                 # This will not handle brackets in braces!
1238                 if ($outstr =~ /\]/) { # protect spaces only *until* the bracket
1239                     my $tempstr = $`;
1240                     my $tempstr2 = $';
1241                     # Note that any \t's have been changed to space already
1242                     $tempstr =~ s/ /$dummy/g;
1243
1244                     # Print 1 space after the argument (which finished with ])
1245                     # Don't print 2 (i.e. remove leading space from $tempstr2)
1246                     # don't print the bracket
1247                     $tempstr2 =~ s/^ //;
1248                     $outstr = "$tempstr $tempstr2";
1249                     $protect_spaces = 0; # Done with optional argument
1250                 } else { # protect all spaces, since we're inside brackets
1251                     $outstr =~ s/ /$dummy/g;
1252                 }
1253             } # end special stuff for protected spaces
1254
1255             # Translate any LaTeX text that requires special LyX handling
1256             foreach $dummy (keys %TextTransTable) {
1257                 $outstr =~ s/\Q$dummy\E/$TextTransTable{$dummy}/g;
1258             }
1259
1260             # "pretty-print" the string. It's not perfect, since there may
1261             # be text in the OUTFILE before $outstr, but it will keep from
1262             # having REALLY long lines.
1263             # Try to use approximately the same word-wrapping as LyX does:
1264             # - before space after a period, except at end of string
1265             # - before first space after column seventy-one
1266             # - after 80'th column
1267             while (1) {
1268                 $outstr =~ s/\. (?!$)/.\n /      or
1269                 $outstr =~ s/(.{71,79}?) /$1\n / or
1270                 $outstr =~ s/(.{80})(.)/$1\n$2/ or
1271                 last; # exit loop if string is < 79 characters
1272             }
1273
1274             # Actually print the text
1275             print OUTFILE "$outstr";
1276             last TYPESW;
1277         } # end TT::Text handling
1278
1279         # The default action - this should never happen!
1280         print("I don't know ",$eaten->print) if $debug_on;
1281
1282     } # end for ($type)
1283
1284     print "\n" if $debug_on;
1285
1286 } #end sub basic_lyx
1287
1288 #########################  TEX MODE  SUBROUTINES  #########################
1289
1290 # This subroutine copies and prints a latex token and its arguments if any.
1291 # This sub is only needed if the command was not found in the syntax file
1292 # Use exact_print to preserve, e.g., whitespace after macros
1293 sub copy_latex_unknown {
1294     my $eaten = shift;
1295     my $fileobject = shift;
1296     my $outstr = $eaten->exact_print;
1297     my ($dummy, $tok, $count);
1298
1299 # Copy the actual word. Copy while you've still got
1300 #     arguments. Assume all args must be in the same paragraph
1301 #     (There could be new paragraphs *within* args)
1302     #    We can't use copy_verbatim (unless we make it smarter) because
1303     # it would choke on nested braces
1304     print "\nUnknown token: '",$eaten->print,"': Copying in TeX mode\n"
1305                                                          if $debug_on;
1306     my $dum2;
1307     while (($dum2 = $fileobject->lookAheadToken) &&
1308            ($dum2 =~ /^[[{]$/)) {
1309         if ($dum2 eq '[') { #copy optional argument - assume it's simple
1310             $tok = $fileobject->eatOptionalArgument;
1311             $outstr .= $tok->exact_print; # also print brackets & whitespace
1312         } else {
1313             $count = 0;
1314             EAT: { #copied from eatBalanced, but allow paragraphs
1315                 die unless defined ($tok = $fileobject->eatMultiToken);
1316                 $outstr.="\n",redo EAT if ref($tok) eq "Text::TeX::Paragraph";
1317                 $dummy = $tok->exact_print;
1318                 $outstr .= $dummy;
1319                 # Sometimes, token will be '\n{', e.g.
1320                 $count++ if $dummy =~ /^\s*\{$/; # add a layer of nesting
1321                 $count-- if $dummy =~ /^\s*\}$/; # end one layer of nesting
1322                 redo EAT if $count; #don't dump out until all done nesting
1323             } #end EAT block
1324         } # end if $dummy = [{
1325
1326     } #end while
1327     # Add {} after macro if it's followed by '}'. Otherwise, {\foo}bar
1328     #     will yield \foobar when LyX creates LaTeX files
1329     $outstr.="{}" if $outstr=~/\\[a-zA-Z]+$/ && $dum2 eq '}';
1330
1331     # Print it out in TeX mode
1332     &print_tex_mode($outstr);
1333
1334     print "\nDone copying unknown token" if $debug_on;
1335 } # end sub copy_latex_unknown
1336
1337 # Copy an untranslatable latex command whose syntax we know, along with its
1338 # arguments
1339 #    The command itself, optional arguments, and untranslatable
1340 # arguments get copied in TeX mode. However, arguments which contain regular
1341 # LaTeX will get translated by reLyX. Do that by printing what you have so
1342 # far in TeX mode, leaving this subroutine, continuing with regular reLyX
1343 # translating, and then returning here when you reach the ArgToken or
1344 # EndArgsToken at the end of the translatable argument.
1345 #    We need to keep a stack of the tokens that brought us here, because
1346 # you might have nested commands (e.g., \mbox{hello \fbox{there} how are you}
1347 sub copy_latex_known {
1348     my ($eaten, $fileobject) = (shift,shift);
1349     my $type = ref($eaten);
1350     $type =~ s/^Text::TeX::// or die "unknown token?!";
1351
1352     # token itself for TT::Token, TT::BegArgsToken,
1353     # Corresponding BegArgsToken for ArgToken,EndArgsToken
1354     my $temp_start = $eaten->base_token;
1355
1356 # Initialize tex mode copying
1357     if ($type eq "BegArgsToken" or $type eq "Token") {
1358         print "\nCopying untranslatable token '",$eaten->print,
1359                                       "' in TeX mode" if $debug_on;
1360         push @tex_mode_tokens, $temp_start;
1361
1362         # initialize the string of stuff we're copying
1363         $tex_mode_string = $eaten->exact_print;
1364     } # Start tex copying?
1365
1366 # Handle arguments
1367     # This token's next arguments -- returns a string matching /o*[rR]?/
1368     my $curr_args = $eaten->next_args($fileobject);
1369
1370     if ($type eq "EndArgsToken" or $type eq "ArgToken") {
1371         # Print ending '}' for the group we just finished reading
1372         $tex_mode_string .= '}';
1373     }
1374
1375     # If there could be optional arguments next, copy them
1376     while ($curr_args =~ s/^o// && $fileobject->lookAheadToken eq '[') {
1377         my $opt = $fileobject->eatOptionalArgument;
1378         $tex_mode_string .= $opt->exact_print;
1379     }
1380     $curr_args =~ s/^o*//; # Some OptArgs may not have appeared
1381
1382     if ($type eq "BegArgsToken" or $type eq "ArgToken") {
1383         # Print beginning '{' for the group we're about to read
1384         $tex_mode_string .= '{';
1385     }
1386
1387     # Now copy the next required argument, if any
1388     # Copy it verbatim (r), or translate it as regular LaTeX (R)?
1389     if ($curr_args =~ s/^r//) {
1390         my $group = $fileobject->eatRequiredArgument;
1391         my $temp = $group->exact_print;
1392         # Remove braces. They're put in explicitly
1393         $temp =~ s/\{(.*)\}/$1/; # .* is greedy
1394         $tex_mode_string .= $temp;
1395
1396     } elsif ($curr_args =~ s/^R//) {
1397         print "\n" if $debug_on;
1398         &print_tex_mode($tex_mode_string);
1399         $tex_mode_string = "";
1400         print "\nTranslating this argument for ",$temp_start->print,
1401               " as regular LaTeX" if $debug_on;
1402
1403     } else { # anything but '' is weird
1404         warn "weird arg $curr_args to ",$temp_start->print,"\n" if $curr_args;
1405     }
1406
1407 # Finished tex mode copying
1408     if ($type eq "Token" or $type eq "EndArgsToken") {
1409
1410         # Add {} to plain tokens followed by { or }. Otherwise {\foo}bar
1411         # and \foo{bar} yield \foobar in the LaTeX files created by LyX
1412         my $dummy;
1413         if ($type eq "Token" and
1414                 $dummy=$fileobject->lookAheadToken and
1415                 $dummy =~ /[{}]/)
1416         {
1417             $tex_mode_string .= '{}';
1418         }
1419
1420         # Print out the string
1421         print "\n" if $debug_on;
1422         &print_tex_mode($tex_mode_string);
1423         $tex_mode_string = "";
1424
1425         # We're done with this token
1426         pop(@tex_mode_tokens);
1427
1428         my $i = $type eq "Token" ? "" : " and its arguments";
1429         my $j = $temp_start->print;
1430         print "\nDone copying untranslatable token '$j'$i in TeX mode"
1431                                                           if $debug_on;
1432     } # end tex copying?
1433 } # end sub copy_latex_known
1434
1435 # Print a string in LyX "TeX mode"
1436 #    The goal of this subroutine is to create a block of LyX which will be
1437 # translated exactly back to the original when LyX creates its temporary LaTeX
1438 # file prior to creating a dvi file.
1439 #    Don't print \n\n at the end of the string... instead just set the new
1440 # paragraph flag. Also, don't print whitespace at the beginning of the string.
1441 # Print nothing if it's the beginning of a paragraph, or space otherwise.
1442 # These two things avoid extra C-Enter's in the LyX file
1443 sub print_tex_mode {
1444     my $outstr = shift;
1445
1446     print "'$outstr'" if $debug_on;
1447
1448     # Handle extra \n's (& spaces) at beginning & end of string
1449     my $str_ends_par = ($outstr =~ s/\n{2,}$//);
1450     if ($IsNewParagraph) {
1451         $outstr =~ s/^\s+//;  # .e.g, $outstr follows '\begin{quote}'
1452     } else {
1453         # Any whitespace is equivalent to one space in LaTeX
1454         $outstr =~ s/^\s+/ /; # e.g. $outstr follows "\LaTeX{}" or "Hi{}"
1455     }
1456
1457     # Check whether string came right at the beginning of a new paragraph
1458     # This *might* not be necessary. Probably can't hurt.
1459     &CheckForNewParagraph;
1460
1461     # Get into TeX mode
1462     print OUTFILE "$start_tex_mode";
1463
1464     # Do TeX mode translation;
1465     $outstr =~ s/\\/\n\\backslash /g;
1466     # don't translate \n in '\n\backslash' that we just made!
1467     $outstr =~ s/\n(?!\\backslash)/\n\\newline \n/g;
1468
1469     # Print the actual token + arguments if any
1470     print OUTFILE $outstr;
1471
1472     # Get OUT of LaTeX mode (and end nesting if nec.)
1473     print OUTFILE "$end_tex_mode";
1474     $IsNewParagraph = $str_ends_par;
1475
1476     return;
1477 } # end sub print_tex_mode
1478
1479 ############################  LAYOUT  SUBROUTINES  ###########################
1480
1481 sub CheckForNewParagraph {
1482 # This subroutine makes sure we only write \layout command once per paragraph
1483 #    It's mostly necessary cuz 'Standard' layout is the default in LaTeX;
1484 #    there is no command which officially starts a standard environment
1485 # If we're in a table, new layouts aren't allowed, so just return
1486 # If $IsNewParagraph is 0, it does nothing
1487 # If $INP==1, It starts a new paragraph
1488 # If $CurrentAlignment is set, it prints the alignment command for this par.
1489 # If $MayBeDeeper==1 and we're currently within a list environment,
1490 #    it starts a "deeper" Standard paragraph
1491     my $dummy; 
1492     my $layout = $$CurrentLayoutStack[-1];
1493
1494     return if &RelyxTable::in_table;
1495
1496     if ($IsNewParagraph) {
1497         # Handle standard text within a list environment specially
1498         if ($MayBeDeeper) {
1499             if ($layout =~ /^$ListLayouts$/o) {
1500                 push (@$CurrentLayoutStack, "Standard");
1501                 print "\nCurrent Layout Stack: @$CurrentLayoutStack\n"
1502                                                      if $debug_on;
1503                 print OUTFILE "\n\\begin_deeper ";
1504                 $layout = "Standard";
1505             }
1506             $MayBeDeeper = 0; # Don't test again until new paragraph
1507         }
1508
1509         # Print layout command itself
1510         print OUTFILE "\n\\layout $layout\n\n";
1511         print OUTFILE $CurrentAlignment if $CurrentAlignment;
1512
1513         # Now that we've written the command, it's no longer a new paragraph
1514         $IsNewParagraph = 0;
1515
1516         # And we're no longer waiting to see if this paragraph is empty
1517         $PendingLayout = 0;
1518
1519         # When you write a new paragraph, reprint any font commands
1520         foreach $dummy (keys %$CurrentFontStatus) {
1521             my $currf = $CurrentFontStatus->{$dummy}->[-1];
1522             if ($currf ne "default") {
1523                 print OUTFILE "\n$dummy $currf \n";
1524             }
1525         }
1526     } # end if $INP
1527 } # end sub CheckForNewParagraph
1528
1529 sub ConvertToLayout {
1530 # This subroutine begins a new layout, pushing onto the layout stack, nesting
1531 # if necessary. It doesn't actually write the \layout command -- that's
1532 # done by CheckForNewParagraph.
1533 #    The subroutine assumes that it's being passed an environment name or macro
1534 # which is known and which creates a known layout
1535 #    It uses the ToLayout hash (created by the ReadCommands module) which
1536 # gives the LyX layout for a given LaTeX command or environment
1537 # Arg0 is the environment or macro
1538     my $name = shift;
1539
1540     my $layoutref = $ReadCommands::ToLayout->{$name};
1541     my $layout = $layoutref->{'layout'};
1542     my $keepempty = $layoutref->{'keepempty'};
1543     my $dummy = ($name =~ /^\\/ ? "macro" : "environment");
1544     print "\nChanging $dummy $name to layout $layout" if $debug_on;
1545
1546     # Nest if the layout stack has more than just "Standard" in it
1547     if ($#{$CurrentLayoutStack} > 0) {
1548         # Die here for sections & things that can't be nested!
1549         print " Nesting!" if $debug_on;
1550         print OUTFILE "\n\\begin_deeper ";
1551     }
1552
1553     # If we still haven't printed the *previous* \layout command because that
1554     # environment is empty, print it now! (This happens if an environment
1555     # is nested inside a keepempty, like slide.)
1556     &CheckForNewParagraph if $PendingLayout;
1557
1558     # Put the new layout onto the layout stack
1559     push @$CurrentLayoutStack, $layout;
1560     print "\nCurrent Layout Stack: @$CurrentLayoutStack" if $debug_on;
1561
1562     # Upcoming text will be new paragraph, needing a new layout cmd
1563     $IsNewParagraph = 1;
1564
1565     # Test for nested "Standard" paragraph in upcoming text?
1566     # Some environments can nest. Sections & Title commands can't
1567     $MayBeDeeper = $layoutref->{"nestable"};
1568
1569     # We haven't yet read any printable stuff in the new paragraph
1570     # If this is a layout that's allowed to be empty, prepare for an
1571     # empty paragraph
1572     $PendingLayout = $keepempty;
1573
1574 } # end sub ConvertToLayout
1575
1576 sub EndLayout {
1577 # This subroutine ends a layout, popping the layout stack, etc.
1578 #    The subroutine assumes that it's being passed an environment name or macro
1579 # which is known and which creates a known layout
1580 #    It uses the ToLayout hash (created by the ReadCommands module) which
1581 # gives the LyX layout for a given LaTeX command or environment
1582 # Arg0 is the environment or macro
1583     my $name = shift;
1584
1585     my $layoutref = $ReadCommands::ToLayout->{$name};
1586     my $layout = $layoutref->{'layout'};
1587     my $dummy = ($name =~ /^\\/ ? "macro" : "environment");
1588     print "\nEnding $dummy $name (layout $layout)" if $debug_on;
1589
1590     # If we still haven't printed the *previous* \layout command because that
1591     # environment is empty, print it now! Before we pop the stack!
1592     # This happens for a totally empty, non-nesting environment,
1593     # like hollywood.sty's fadein
1594     &CheckForNewParagraph if $PendingLayout;
1595
1596     my $mylayout = pop (@$CurrentLayoutStack);
1597
1598     # If a standard paragraph was the last thing in a list, then
1599     #     we need to end_deeper and then pop the actual list layout
1600     # This should only happen if the Standard layout was nested
1601     #    in a nestable layout. We don't check.
1602     if ($mylayout eq "Standard") {
1603         print OUTFILE "\n\\end_deeper ";
1604         print " End Standard Nesting!" if $debug_on;
1605         $mylayout = pop (@$CurrentLayoutStack);
1606     }
1607
1608     # The layout we popped off the stack had better match the
1609     #    environment (or macro) we're ending!
1610     if ($mylayout ne $layout) { die "Problem with Layout Stack!\n"};
1611     print "\nCurrent Layout Stack: @$CurrentLayoutStack" if $debug_on;
1612
1613     # If we're finishing a nested layout, we need to end_deeper
1614     # This should only happen if the layout was nested
1615     #    in a nestable layout. We don't check.
1616     # Note that if we're nested in a list environment and the
1617     #     NEXT paragraph is Standard, then we'll have an extra
1618     #     \end_deeper \begin_deeper in the LyX file. It's sloppy
1619     #     but it works, and LyX will get rid of it when it
1620     #     resaves the file.
1621     if ($#{$CurrentLayoutStack} > 0) {
1622         print " End Nesting!" if $debug_on;
1623         print OUTFILE "\n\\end_deeper ";
1624     }
1625
1626     # Upcoming text will be new paragraph, needing a new layout cmd
1627     $IsNewParagraph = 1;
1628
1629     # Test for nested "Standard" paragraph in upcoming text?
1630     # Some environments can nest. Sections & Title commands can't
1631     $MayBeDeeper = $layoutref->{"nestable"};
1632 } # end sub EndLayout
1633
1634 #######################  MISCELLANEOUS SUBROUTINES  ###########################
1635 sub fixmath {
1636 # Translate math commands LyX doesn't support into commands it does support
1637     my $input = shift;
1638     my $output = "";
1639
1640     while ($input =~ s/
1641             (.*?)    # non-token matter ($1)
1642             (\\      # token ($2) is a backslash followed by ...
1643                 ( ([^A-Za-z] \*?) |    # non-letter (and *) ($4) OR
1644                   ([A-Za-z]+ \*?)   )  # letters (and *) ($5)
1645             )//xs) # /x to allow whitespace/comments, /s to copy \n's
1646     { 
1647         $output .= $1;
1648         my $tok = $2;
1649         if (exists $ReadCommands::math_trans{$tok}) {
1650             $tok = $ReadCommands::math_trans{$tok};
1651             # add ' ' in case we had, e.g., \|a, which would become \Verta
1652             # Only need to do it in those special cases
1653             $tok .= ' ' if
1654                     defined $4 && $tok =~ /[A-Za-z]$/ && $input =~ /^[A-Za-z]/;
1655         }
1656         $output .= $tok;
1657     }
1658     $output .= $input; # copy what's left in $input
1659
1660     return $output;
1661 }
1662
1663 1; # return true to calling subroutine
1664