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.
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)
12 use RelyxTable; # Handle LaTeX tables
13 use RelyxFigure; # Handle LaTeX figures
14 use Verbatim; # Copy stuff verbatim
16 use vars qw($bibstyle_insert_string $bibstyle_file $Begin_Inset_Include);
17 $bibstyle_insert_string = "%%%%%Insert bibliographystyle file here!";
19 $Begin_Inset_Include = "\\begin_inset Include";
21 #################### PACKAGE-WIDE VARIABLES ###########################
22 my $debug_on; # is debugging on?
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
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;
41 # HACK to protect spaces within optional argument to \item
42 my $protect_spaces = 0;
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
47 my $OldMBD; #Save $MBD during footnotes -- this should very rarely be necessary!
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;
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
61 '\emph' => ["default"],
62 '\family' => ["default"],
63 '\series' => ["default"],
64 '\shape' => ["default"],
65 '\bar' => ["default"],
66 '\size' => ["default"],
67 '\noun' => ["default"],
69 my $CurrentFontStatus = \%FontStatus;
71 # Currently aligning paragraphs right, left, or center?
72 my $CurrentAlignment = "";
73 my $OldAlignment; # Save $AS during footnotes
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
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";
83 # String to write before each item
84 my $item_preface = "";
86 ############# INFORMATION ABOUT LATEX AND LYX #############################
87 # LyX translations of LaTeX font commands
88 my %FontTransTable = (
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
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",
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",
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",
134 # Simple LaTeX tokens which are turned into small pieces of LyX text
135 my %TextTokenTransTable = (
136 # LaTeX escaped characters
146 # \i and \j are used in accents. LyX doesn't recognize them in plain
147 # text. Hopefully, this isn't a problem.
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",
158 '\LaTeXe' => "LaTeX2e",
161 '\lyxarrow' => "\\SpecialChar \\menuseparator\n",
162 '\hfill' => "\n\\hfill \n",
163 '\noindent' => "\n\\noindent \n",
164 '\textbackslash' => "\n\\backslash \n",
165 '\textgreater' => ">",
168 '\textasciitilde' => "~",
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 = (
175 "``" => "\n\\begin_inset Quotes eld\n\\end_inset \n\n",
176 "''" => "\n\\begin_inset Quotes erd\n\\end_inset \n\n",
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",
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);
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\"]";
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",
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
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";
221 # passed a string and an array
222 # returns true if the string is an element of the array.
225 return grep {$_ eq $name} @_;
228 my @NatbibCommands = map {"\\$_"} qw(citet citealt citep citealp citeauthor);
231 # returns true if it is a valid natbib citation
232 sub isNatbibCitation {
235 # These two have a single form
236 return 1 if ($name eq '\citeyear' or $name eq '\citeyearpar');
238 # Natbib citations can start with a 'C' or a 'c'
239 $name =~ s/^\\C/\\c/;
240 # The can end with a '*'
242 # Is this doctored string found in the list of valid commands?
243 return foundIn($name, @NatbibCommands);
247 ##################### PARSER INVOCATION ##################################
249 # This subroutine calls the TeX parser & translator
250 # Before it does that, it does lots of setup work to get ready for parsing.
251 # Arg0 is the file to read (clean) LaTeX from
252 # Arg1 is the file to write LyX to
253 # Arg2 is the file to read layouts from (e.g., in LYX_DIR/layouts/foo.layout)
255 my ($InFileName, $OutFileName) = (shift,shift);
257 # Before anything else, set the package-wide variables based on the
259 # opt_d is set to 1 if '-d' option given, else (probably) undefined
260 $debug_on = (defined($main::opt_d) && $main::opt_d);
262 # Hash of tokens passed to the TeX parser
263 # Many values are $Text::TeX::Tokens{'\operatorname'}, which has
264 # Type=>report_args and count=>1
265 # Note that we don't have to bother putting in tokens which will be simply
266 # translated (e.g., from %TextTokenTransTable).
268 '{' => $Text::TeX::Tokens{'{'},
269 '}' => $Text::TeX::Tokens{'}'},
270 '\begin' => $Text::TeX::Tokens{'\begin'},
271 '\end' => $Text::TeX::Tokens{'\end'},
273 # Lots of other commands will be made by ReadCommands:Merge
274 # by reading the LaTeX syntax file
276 # Font sizing commands (local)
277 '\tiny' => {Type => 'local'},
278 '\small' => {Type => 'local'},
279 '\scriptsize' => {Type => 'local'},
280 '\footnotesize' => {Type => 'local'},
281 '\small' => {Type => 'local'},
282 '\normalsize' => {Type => 'local'},
283 '\large' => {Type => 'local'},
284 '\Large' => {Type => 'local'},
285 '\LARGE' => {Type => 'local'},
286 '\huge' => {Type => 'local'},
287 '\Huge' => {Type => 'local'},
289 # Tokens to ignore (which make a new paragraph)
290 # Just pretend they actually ARE new paragraph markers!
291 '\maketitle' => {'class' => 'Text::TeX::Paragraph'},
294 # Now add to MyTokens all of the commands that were read from the
295 # commands file by package ReadCommands
296 &ReadCommands::Merge(\%MyTokens);
298 # Here's the actual subroutine. The above is all preparation
300 my $zzz = $debug_on ? " ($InFileName --> $OutFileName)\n" : "... ";
301 print STDERR "Translating$zzz";
302 open (OUTFILE,">$OutFileName");
304 # Open the file to turn into LyX.
305 my $infile = new Text::TeX::OpenFile $InFileName,
306 'defaultact' => \&basic_lyx,
307 'tokens' => \%MyTokens;
309 # Process what's left of the file (everything from \begin{document})
312 # Last line of the LyX file
313 print OUTFILE "\n\\the_end\n";
315 #warn "Done with basic translation\n";
317 } # end subroutine call_parser
319 # This is used as a toggle so that we know what to do when basic_lyx is
320 # passed a '$' or '$$' token.
326 if ($name eq '\(' || $name eq '\[' ||
327 # These tokens bound both ends of a math environment so we must check
328 # $inside_math to know what action to take.
329 ($name eq '$' || $name eq '$$') && !$inside_math) {
342 if ($name eq '\)' || $name eq '\]' ||
343 # These tokens bound both ends of a math environment so we must check
344 # $inside_math to know what action to take.
345 ($name eq '$' || $name eq '$$') && $inside_math) {
356 sub regularizeLatexLength {
357 my $LatexLength = shift;
359 # Remove any whitespace
360 $LatexLength =~ s/\s//g;
361 # Remove a leading '+' as unnecessary
362 $LatexLength =~ s/^\+?(\d)/$1/;
363 # Split into value and unit parts
366 if ($LatexLength =~ /(-?\d+[.,]?\d*)(\\?[a-zA-Z]*)$/) {
370 # If the input is invalid, return what we have.
371 return $LatexLength if ($val eq '' || $unit eq '');
373 # '4,5' is a valid LaTeX number. Change it to '4.5'
375 # If the unit is not a LaTeX macro, then ensure it is lower case
376 if (!($unit =~ /^\\/)) {
377 $unit =~ s/([a-z]*)/\L$1/i;
380 $LatexLength = $val . $unit;
386 # Straight translation of LaTeX lengths to LyX ones.
387 my %lengthAsLyXString = ('\textwidth' => 'text%',
388 '\columnwidth' => 'col%',
389 '\paperwidth' => 'page%',
390 '\linewidth' => 'line%',
391 '\paperheight' => 'pheight%',
392 '\textheight' => 'theight%');
394 my $LatexLength = shift;
395 $LatexLength = regularizeLatexLength($LatexLength);
397 my $LyXLength = $LatexLength;
398 # If $LatexLength is something like '4.5\columnwidth', translate into
400 if ($LatexLength =~ /([+-]?\d+\.?\d*)(\\[a-z]*)/) {
401 if (defined($lengthAsLyXString{$2})) {
402 $LyXLength = ($1 * 100) . $lengthAsLyXString{$2};
409 ########################## MAIN TRANSLATOR SUBROUTINE #####################
411 # This subroutine is called by Text::TeX::process each time subroutine
412 # eat returns a value.
413 # Argument 0 is the return value from subroutine eat
414 # Argument 1 is the Text::TeX::OpenFile (namely, $TeXfile)
416 my $fileobject = shift;
418 # This handles most but maybe not all comments
419 # THere shouldn't be any if we've run CleanTeX.pl
420 print "Comment: ",$eaten->comment if defined $eaten->comment && $debug_on;
422 my $type = ref($eaten);
423 print "$type " if $debug_on;
425 # This loop is basically just a switch. However, making it a for
426 # (1) makes $_ = $type (convenient)
427 # (2) allows us to use things like next and last
428 TYPESW: for ($type) {
431 s/^Text::TeX:://o or die "unknown token?!";
435 # The parser puts whitespace before certain kinds of tokens along
436 # with that token. If that happens, save a space
437 my $pre_space = ""; # whitespace before a token
438 if (/BegArgsToken|^Token|::Group$/) {
439 $dummy = $eaten->exact_print;
440 # Only set prespace if we match something
441 # We wouldn't want it to be more than one space, since that's
442 # illegal in LyX. Also, replace \t or \n with ' ' since they are
443 # ignored in LyX. Hopefully, this won't lead to anything worse
444 # than some lines with >80 chars
445 # Finally, don't put a space at the beginning of a new paragraph
446 if (($dummy =~ /^\s+/) && !$IsNewParagraph) {
451 # Handle blank lines.
452 if (m/^Paragraph$/o) {
453 # $INP <>0 means We will need a new layout command
456 # $MBD means start a begin_deeper within list environments
457 # unless there's an \item command
463 # If, e.g., there's just a comment in this token, don't do anything
464 # This actually shouldn't happen if CleanTeX has already removed them
465 last TYPESW if !defined $eaten->print;
467 # Handle LaTeX tokens
470 my $name = $eaten->token_name; # name of the token, e.g., "\large"
471 print "'$name' " if $debug_on;
473 # Tokens which turn into a bit of LyX text
474 if (exists $TextTokenTransTable{$name}) {
475 &CheckForNewParagraph; #Start new paragraph if necessary
477 my $to_print = $TextTokenTransTable{$name};
479 # \@ has to be specially handled, because it depends on
480 # the character AFTER the \@
482 my $next = $fileobject->eatGroup(1);
484 $ch = $next->print or warn "\@ confused me!\n";
486 # Note: \@ CAN'T have pre_space before it
487 print OUTFILE "$to_print$ch\n";
488 print "followed by $ch" if $debug_on;
490 warn "LyX (or LaTeX) can't handle '$ch' after $name\n";
494 } else { # it's not \@
495 # Print the translated text (include preceding whitespace)
496 print OUTFILE "$pre_space$to_print";
497 } # end special handling for \@
499 # Handle tokens that LyX translates as a "LatexCommand" inset
500 } elsif (foundIn($name, @LatexCommands) ||
501 isNatbibCitation($name)){
502 &CheckForNewParagraph; #Start new paragraph if necessary
503 print OUTFILE "$pre_space\n\\begin_inset LatexCommand ",
505 "\n\n\\end_inset \n\n";
507 # Math -- copy verbatim until you're done
508 } elsif (starting_math($name)) {
509 print "\nCopying math beginning with '$name'\n" if $debug_on;
510 # copy everything until end text
511 $dummy = &Verbatim::copy_verbatim($fileobject, $eaten);
512 $dummy = &fixmath($dummy); # convert '\sp' to '^', etc.
514 &CheckForNewParagraph; # math could be first thing in a par
515 print OUTFILE "$pre_space\n\\begin_inset Formula $name ";
516 print $dummy if $debug_on;
517 print OUTFILE $dummy;
519 } elsif (ending_math($name)) {
521 print OUTFILE "$name\n\\end_inset \n\n";
522 print "\nDone copying math ending with '$name'" if $debug_on;
524 # Items in list environments
525 } elsif ($name eq '\item') {
527 # What if we had a nested "Standard" paragraph?
528 # Then write \end_deeper to finish the standard layout
529 # before we write the new \layout ListEnv command
530 if ($$CurrentLayoutStack[-1] eq "Standard") {
531 pop (@$CurrentLayoutStack); # take "Standard" off the stack
532 print OUTFILE "\n\\end_deeper ";
533 print "\nCurrent Layout Stack: @$CurrentLayoutStack"
537 # Upcoming text (the item) will be a new paragraph,
538 # requiring a new layout command based on whichever
539 # kind of list environment we're in
542 # But since we had an \item command, DON'T nest a
543 # deeper "Standard" paragraph in the list
546 # Check for an optional argument to \item
547 # If so, within the [] we need to protect spaces
548 # TODO: In fact, for description, if there's no [] or
549 # there's an empty [], then we need to write a ~, since LyX
550 # will otherwise make the next word the label
551 # If it's NOT a description & has a [] then we're stuck!
552 # They need to fix the bullets w/in lyx!
553 if (($dummy = $fileobject->lookAheadToken) &&
554 ($dummy =~ /\s*\[/)) {
555 $fileobject->eatFixedString('\['); # eat the [
559 # Special lists (e.g. List layout) have to print something
560 # before each item. In that case, CFNP and print it
562 &CheckForNewParagraph;
563 print OUTFILE $item_preface;
566 # Font sizing commands
567 # (Other font commands are TT::BegArgsTokens because they take
568 # arguments. Font sizing commands are 'local' TT::Tokens)
569 } elsif (exists $FontTransTable{$name}) {
570 my $command = $FontTransTable{$name}; #e.g., '\size large'
572 if (! $IsNewParagraph) {
573 print OUTFILE "$pre_space$command";
574 } #otherwise, wait until we've printed the new paragraph command
576 # Store the current font change
577 ($dummy = $command) =~ s/\s*(\S+)\s+(\w+)\s*/$1/;
578 die "Font command error" if !exists $$CurrentFontStatus{$dummy};
579 push (@{$CurrentFontStatus->{$dummy}}, $2);
580 print "\nCurrent $dummy Stack: @{$CurrentFontStatus->{$dummy}}"
584 } elsif ($name eq '&') {
585 if ($thistable = &RelyxTable::in_table) {
586 print OUTFILE "\n\\newline \n";
588 } else {warn "& is illegal outside a table!"}
590 } elsif ($name eq '\\\\' || $name eq '\\newline' || $name eq "\\tabularnewline") {
591 &CheckForNewParagraph; # could be at beginning of par?
592 print OUTFILE "\n\\newline \n";
594 # If we're in a table, \\ means add a row to the table
595 # Note: if we're on the last row of the table, this extra
596 # row will get deleted later. This hack is necessary, because
597 # we don't know while reading when the last row is!
598 if ($thistable = &RelyxTable::in_table) {
602 } elsif ($name eq '\hline') {
603 if ($thistable = &RelyxTable::in_table) {
604 # hcline does hline if no arg is given
606 } else {warn "\\hline is illegal outside a table!"}
610 } elsif ($name =~ /^\\epsf[xy]size$/) {
611 # We need to eat '=' followed by EITHER more text OR
612 # one (or more?!) macros describing a TeX size
613 my $arg = $fileobject->eatMultiToken;
614 my $length = $arg->print;
615 $length =~ s/^\s*=\s*// or warn "weird '$name' command!";
617 # If there's no "cm" or other letters in $length, the next token
618 # ought to be something like \textwidth. Then it will be empty
619 # or just have numbers in it.
620 # This is bugprone. Hopefully not too many people use epsf!
621 if ($length =~ /^[\d.]*\s*$/) {
622 my $next = $fileobject->eatMultiToken;
623 $length .= $next->print;
625 $length =~ s/\s*$//; # may have \n at end
627 # If we can't parse the command, print it in tex mode
628 &RelyxFigure::parse_epsfsize($name, $length) or
629 &print_tex_mode("$name=$length");
633 } elsif ($name =~ /\\verb.*?/) {
634 my $dummy = &Verbatim::copy_verb($fileobject, $eaten);
635 print "\nCopying $name in TeX mode: " if $debug_on;
636 &print_tex_mode ($dummy);
638 # Otherwise it's an unknown token, which must be copied
639 # in TeX mode, along with its arguments, if any
641 if (defined($eaten->relyx_args($fileobject))) {
642 ©_latex_known($eaten, $fileobject);
643 } else { # it's not in MyTokens
644 ©_latex_unknown($eaten, $fileobject);
651 # Handle tokens that take arguments, like \section{},\section*{}
652 if (/^BegArgsToken$/) {
653 my $name = $eaten->token_name;
654 print "$name" if $debug_on;
656 # Handle things that LyX translates as a "LatexCommand" inset
657 if (foundIn($name, @LatexCommands) || isNatbibCitation($name)){
658 &CheckForNewParagraph; #Start new paragraph if necessary
660 print OUTFILE "$pre_space\n\\begin_inset LatexCommand ";
662 # \bibliography gets handled as a LatexCommand inset, but
663 # it's a special case, cuz LyX syntax expects "\BibTeX"
664 # instead of "\bibliography" (I have no idea why), and because
665 # we have to print extra stuff
666 # Because we might not have encountered the
667 # \bibliographystyle command yet, we write
668 # "insert bibstyle here", and replace that string
669 # with the actual bibliographystyle argument in
670 # LastLyX (i.e., the next pass through the file)
671 if ($name eq "\\bibliography") {
672 print OUTFILE "\\BibTeX[", $bibstyle_insert_string, "]";
674 print OUTFILE "$name";
677 # \cite takes an optional argument, e.g.
678 my $args = $eaten->relyx_args ($fileobject);
679 while ($args =~ s/^o//) {
680 my $tok = $fileobject->eatOptionalArgument;
681 my $dummy = $tok->exact_print;
682 print OUTFILE $dummy;
686 last TYPESW; # skip to the end of the switch
689 if (grep {$_ eq $name} @IncludeCommands) {
690 &CheckForNewParagraph; #Start new paragraph if necessary
691 print OUTFILE "$pre_space\n$Begin_Inset_Include $name\{";
692 last TYPESW; # skip to the end of the switch
695 # This is to handle cases where _ is used, say, in a filename.
696 # When _ is used in math mode, it'll be copied by the math mode
697 # copying subs. Here we handle cases where it's used in non-math.
698 # Examples are filenames for \include & citation labels.
699 # (It's illegal to use it in regular LaTeX text.)
701 print OUTFILE $eaten->exact_print;
705 # Sectioning and Title environments (using a LyX \layout command)
706 if (exists $ReadCommands::ToLayout->{$name}) {
707 &ConvertToLayout($name);
708 last TYPESW; #done translating
710 # Font characteristics
711 } elsif (exists $FontTransTable{$name}) {
713 my $command = $FontTransTable{$name};
714 ($dummy, $dum2) = ($command =~ /(\S+)\s+(\w+)/);
716 # HACK so that "\emph{hi \emph{bye}}" yields unemph'ed "bye"
717 if ( ($dummy eq "\\emph") &&
718 ($CurrentFontStatus->{$dummy}->[-1] eq "on")) {
719 $dum2 = "default"; # "on" instead of default?
720 $command =~ s/on/default/;
723 # If we're about to start a new paragraph, save writing
724 # this command until *after* we write '\layout Standard'
725 if (! $IsNewParagraph) {
726 print OUTFILE "$pre_space$command";
729 # Store the current font change
730 die "Font command error" if !exists $$CurrentFontStatus{$dummy};
731 push (@{$CurrentFontStatus->{$dummy}}, $dum2);
734 # Handle footnotes and margin notes
735 # Make a new font table & layout stack which will be local to the
736 # footnote or marginpar
737 } elsif (exists $FloatTransTable{$name}) {
738 my $command = $FloatTransTable{$name};
741 print OUTFILE "$pre_space$command";
743 # Make $CurrentFontStatus point to a new (anonymous) font table
744 $CurrentFontStatus = {
745 '\emph' => ["default"],
746 '\family' => ["default"],
747 '\series' => ["default"],
748 '\shape' => ["default"],
749 '\bar' => ["default"],
750 '\size' => ["default"],
751 '\noun' => ["default"],
754 # And make $CurrentLayoutStack point to a new (anon.) stack
755 $CurrentLayoutStack = ["Standard"];
757 # Store whether we're at the end of a paragraph or not
758 # for when we get to end of footnote AND
759 # Note that the footnote text will be starting a new paragraph
760 # Also store the current alignment (justification)
761 $OldINP = $IsNewParagraph; $OldMBD = $MayBeDeeper;
762 $OldAlignment = $CurrentAlignment;
764 $MayBeDeeper = 0; #can't be deeper at beginning of footnote
765 $CurrentAlignment = "";
768 } elsif ($name =~ m/^$AccentTokens$/) {
769 &CheckForNewParagraph; # may be at the beginning of a par
771 print OUTFILE "$pre_space\n",'\i ',$name,'{'
773 # Included Postscript Figures
774 # Currently, all postscript including commands take one
775 # required argument and 0 to 2 optional args, so we can
776 # clump them together in one else.
777 } elsif (grep {$_ eq $name} @GraphicsCommands) {
778 &CheckForNewParagraph; # may be at the beginning of a par
779 my $arg1 = $fileobject->eatOptionalArgument;
780 # arg2 is a token of an empty string for most commands
781 my $arg2 = $fileobject->eatOptionalArgument;
782 my $arg3 = $fileobject->eatRequiredArgument;
783 my $save = $arg1->exact_print . $arg2->exact_print .
786 # Parse and put figure into LyX file
787 # Print it verbatim if we didn't parse correctly
788 my $thisfig = new RelyxFigure::Figure;
789 if ($thisfig->parse_pscommand($name, $arg1, $arg2, $arg3)) {
790 print OUTFILE $thisfig->print_info;
792 &print_tex_mode($eaten->exact_print . $save);
797 } elsif ($name eq "\\multicolumn") {
798 if ($thistable = &RelyxTable::in_table) {
799 # the (simple text) first arg.
800 $dummy = $fileobject->eatRequiredArgument->contents->print;
801 my $group = $fileobject->eatRequiredArgument;
802 $thistable->multicolumn($dummy, $group);
803 } else {warn "\\multicolumn is illegal outside a table!"}
805 } elsif ($name eq '\cline') {
806 if ($thistable = &RelyxTable::in_table) {
807 # the (simple text) first arg.
808 $dummy = $fileobject->eatRequiredArgument->contents->print;
809 # sub hcline does cline if an arg is given
810 $thistable->hcline($dummy);
811 } else {warn "\\cline is illegal outside a table!"}
815 } elsif ($name eq '\bibliographystyle') {
816 $tok = $fileobject->eatRequiredArgument;
818 # There may be >1 token in the {}, e.g. "{a_b}" -> 3 tokens
819 my @toks = $tok->contents;
820 foreach $tok (@toks) {
821 # kludge: CleanTeX adds {} after _
822 $tok = $tok->contents if ref($tok) eq "Text::TeX::Group";
823 $bibstyle_file .= $tok->print;
825 print "\nBibliography style file is $bibstyle_file"if $debug_on;
827 # LyX \bibitem actually looks just like LaTeX bibitem, except
828 # it's in a Bibliography par & there must be a space after the
829 # bibitem command. Note we need to explicitly print the braces...
830 } elsif ($name eq "\\bibitem") {
831 $IsNewParagraph=1; # \bibitem always starts new par. in LyX
832 &CheckForNewParagraph;
834 $tok = $fileobject->eatOptionalArgument;
835 print OUTFILE "$name ", $tok->exact_print, "{";
839 # ensuremath -- copy verbatim until you're done
841 # Note that we'll only get here if the command is NOT in math mode
842 } elsif ($name eq '\ensuremath') {
843 print "\nCopying math beginning with '$name'\n" if $debug_on;
844 my $tok = $fileobject->eatGroup; # eat math expression
845 my $dummy = $tok->exact_print;
846 $dummy =~ s/\{(.*)\}/$1/;
847 $dummy = &fixmath($dummy); # convert '\sp' to '^', etc.
849 &CheckForNewParagraph; # math could be first thing in a par
850 print OUTFILE "$pre_space\n\\begin_inset Formula \\( ";
851 print $dummy if $debug_on;
852 print OUTFILE $dummy;
855 print OUTFILE "\\)\n\\end_inset \n\n";
856 print "\nDone copying math" if $debug_on;
858 # Token in the ReadCommands command list that basic_lyx doesn't
861 ©_latex_known($eaten,$fileobject);
868 # ArgTokens appear when we've used eatRequiredArgument
870 # If we're copying a recognized but untranslatable token in tex mode
871 my $tok = $tex_mode_tokens[-1] || 0;
872 if ($eaten->base_token == $tok) {
873 ©_latex_known($eaten,$fileobject);
879 if (/^EndArgsToken$/) {
880 # If we're copying a recognized but untranslatable token in tex mode
881 my $tok = $tex_mode_tokens[-1] || 0;
882 if ($eaten->base_token eq $tok) {
883 ©_latex_known($eaten,$fileobject);
887 my $name = $eaten->token_name;
888 print "$name" if $debug_on;
890 # Handle things that LyX translates as a "LatexCommand" inset
891 # or "Include" insets
892 if (foundIn($name, @LatexCommands, @IncludeCommands) ||
893 isNatbibCitation($name)){
894 print OUTFILE "\}\n\n\\end_inset \n\n";
896 } elsif (exists $ReadCommands::ToLayout->{$name}) {
899 # Font characteristics
900 # Pop the current FontStatus stack for a given characteristic
901 # and give the new command (e.g., \emph default)
902 } elsif (exists $FontTransTable{$name}) {
903 my $command = $FontTransTable{$name};
904 ($dummy) = ($command =~ /(\S+)\s+\w+/);
905 pop @{$CurrentFontStatus->{$dummy}};
906 $command = "\n$dummy $CurrentFontStatus->{$dummy}->[-1] \n";
907 print OUTFILE "$command";
909 # Footnotes and marginpars
910 } elsif (exists $FloatTransTable{$name}) {
911 print OUTFILE "\n\\end_float \n\n";
913 # Reset the layout stack and font status table pointers to
914 # point to the global stack/table again, instead of the
915 # footnote-specific stack/table
916 $CurrentFontStatus = \%FontStatus;
917 $CurrentLayoutStack = \@LayoutStack;
919 # We need to reissue any font commands (but not layouts)
920 foreach $dummy (keys %$CurrentFontStatus) {
921 if ($CurrentFontStatus->{$dummy}->[-1] ne "default") {
922 print OUTFILE $FontTransTable{$dummy};
926 # Same paragraph status as we had before the footnote started
927 $IsNewParagraph = $OldINP; $MayBeDeeper = $OldMBD;
928 $CurrentAlignment = $OldAlignment;
930 } elsif ($name =~ m/^$AccentTokens$/) {
933 } elsif ($name eq "\\bibitem") {
939 } # end if EndArgsToken
941 # Handle END of scope of local commands like \large
943 my $name = $eaten->token_name; #cmd we're ending, e.g.,\large
944 print $name if $debug_on;
946 if (exists $FontTransTable{$name}) {
947 my $command = $FontTransTable{$name};
948 ($dummy = $command) =~ s/\s*(\S*)\s+(\w+)\s*/$1/; #e.g., '\size'
949 die "Font command error" if !exists $$CurrentFontStatus{$dummy};
950 # TT::OF->check_presynthetic returns local commands FIFO!
951 # So pop font stack, but warn if we pop the wrong thing
952 warn " font confusion?" if
953 pop @{$CurrentFontStatus->{$dummy}} ne $2;
954 print "\nCurrent $dummy Stack: @{$CurrentFontStatus->{$dummy}}"
956 my $newfont = $CurrentFontStatus->{$dummy}->[-1];
957 $command = "\n$dummy $newfont\n";
958 print OUTFILE "$command";
961 warn "Unknown EndLocal token!\n";
967 # We don't print { or }, but we make sure that the spacing is correct
969 if (/^Begin::Group$/) {
970 print OUTFILE "$pre_space";
975 if (/^End::Group$/) {
976 print OUTFILE "$pre_space";
981 if (/^Begin::Group::Args$/) {
982 print $eaten->print," " if $debug_on; # the string "\begin{foo}"
983 my $env = $eaten->environment;
985 # Any environment found in the layouts files
986 if (exists $ReadCommands::ToLayout->{$env}) {
987 &ConvertToLayout($env);
989 # Some environments have an extra argument. In that case,
990 # print the \layout command (cuz these environments always
991 # start new pars). Then either print the extra arg or
992 # ignore it (depending on the environment).
993 if (exists $ExtraArgEnvironments{$env}) {
994 # Should be just one token in the arg.
995 my $arg = $fileobject->eatBalanced->contents->print;
997 if ($ExtraArgEnvironments{$env}) { #print it
998 print "\nArgument $arg to $env environment"
1000 $item_preface = $ExtraArgEnvironments{$env} . $arg."\n";
1003 print "\nIgnoring argument '$arg' to $env environment"
1006 } # end if for reading extra args to \begin command
1009 } elsif ($env =~ /^$MathEnvironments$/o) {
1010 &CheckForNewParagraph; # may be beginning of paragraph
1011 my $begin_text = $eaten->print;
1012 print "\nCopying math beginning with '$begin_text'\n"
1014 print OUTFILE "\n\\begin_inset Formula $begin_text ";
1015 $dummy = &Verbatim::copy_verbatim($fileobject, $eaten);
1016 $dummy = &fixmath($dummy); # convert '\sp' to '^', etc.
1017 print $dummy if $debug_on;
1018 print OUTFILE $dummy;
1020 # Alignment environments
1021 } elsif (exists $AlignEnvironments{$env}) {
1022 # Set it to the command which creates this alignment
1023 $CurrentAlignment = $AlignEnvironments{$env};
1024 ($dummy) = ($CurrentAlignment =~ /\S+\s+(\w+)/);
1025 print "\nNow $dummy-aligning text " if $debug_on;
1027 # alignment environments automatically start a new paragraph
1028 $IsNewParagraph = 1;
1030 # Environments lyx translates to floats
1031 } elsif (exists $FloatEnvTransTable{$env}) {
1032 # this really only matters if it's at the very
1033 # beginning of the doc.
1034 &CheckForNewParagraph;
1036 $tok = $fileobject->eatOptionalArgument;
1037 if ($tok && defined ($dummy = $tok->print) && $dummy) {
1038 print "\nIgnoring float placement '$dummy'" if $debug_on;
1040 my $command = $FloatEnvTransTable{$env};
1042 # Open the table/figure
1043 print OUTFILE "$command";
1046 } elsif ($env =~ /^tabular$/) { # don't allow tabular* or ctabular
1047 # Table must start a new paragraph
1048 $IsNewParagraph = 1; $MayBeDeeper = 1;
1049 # We want to print table stuff *after* a \layout Standard
1050 &CheckForNewParagraph;
1052 # Since table info needs to come *before* the table content,
1053 # put a line in the output file saying that the *next*
1054 # reLyX pass needs to put the table info there
1055 print OUTFILE "\n$RelyxTable::TableBeginString\n";
1057 # Read and ignore an optional argument [t] or [b]
1058 $tok = $fileobject->eatOptionalArgument;
1059 if ($tok && defined ($dummy = $tok->print) && $dummy) {
1060 print "\nIgnoring positioning arg '$dummy'" if $debug_on;
1063 # Read the argument into a TT::Group
1064 # (that group may contain groups, e.g. for {clp{10cm}}
1065 $tok = $fileobject->eatGroup;
1066 new RelyxTable::Table $tok;
1069 } elsif ($env eq "minipage") {
1070 &CheckForNewParagraph;
1072 print OUTFILE "\\begin_inset Minipage\n";
1074 # The minipage environment is defined as:
1075 # \begin{minipage}[pos][height][inner-pos]{width} <text>
1078 # Read the position optional argument, if it exists
1079 $tok = $fileobject->eatOptionalArgument;
1080 my $pos = $tok->print if defined($tok->print);
1082 my %map = ('t' => '0', 'c' => '1', 'b' => '2');
1083 if ($debug_on && $pos ne '' && !defined($map{$pos})) {
1084 print "\nIgnoring unknown positioning arg '$pos'\n";
1087 # The minipage is centred by default.
1088 $pos = '1' if (!defined($map{$pos}) ||
1089 ($pos = $map{$pos}) eq '');
1091 # Read the height optional argument, if it exists
1093 $tok = $fileobject->eatOptionalArgument;
1094 if (defined($tok->print)) {
1095 $height = getAsLyXLength($tok->print);
1098 # Read the inner-pos optional argument, if it exists
1099 my $innerpos = $pos;
1100 $tok = $fileobject->eatOptionalArgument;
1101 if (defined($tok->print)) {
1102 my $arg = $tok->print;
1103 print("\nMinipage inner-pos argument, $arg, is ",
1104 "currently ignored\n");
1107 # Read the width as (a reference to) an array of tokens.
1108 $tok = $fileobject->eatBalanced;
1109 my $width = getAsLyXLength($tok->exact_print);
1111 print OUTFILE "position $pos\n";
1112 print OUTFILE "inner_position $innerpos\n";
1113 print OUTFILE "height \"$height\"\n";
1114 print OUTFILE "width \"$width\"\n";
1115 print OUTFILE "collapsed false\n";
1118 } elsif ($env eq "document") {
1120 #print "\nStarting to translate actual document" if $debug_on;
1122 # Special environments to copy as regular text (-r option).
1123 # Do this by copying the \begin & \end command in TeX mode
1124 # (\Q\E around $env allows *'s in environment names!)
1125 } elsif (grep /^\Q$env\E$/, @ReadCommands::regular_env) {
1126 print "\nCopying $env environment as regular text\n"
1128 $dummy = $eaten->print; # \begin{env}, ignore initial whitespace
1129 &print_tex_mode($dummy);
1131 # otherwise, it's an unknown environment
1132 # In that case, copy everything up to the \end{env}
1133 # Save stuff in global tex_mode_string so we can print it
1134 # when we read & handle the \end{env}
1137 print "\nUnknown environment $env" if $debug_on;
1138 $tex_mode_string = "";
1139 # print "\begin{env}
1140 # For reLyXskip env, don't print the \begin & \end commands!
1141 $tex_mode_string .= $eaten->exact_print
1142 unless $env eq "reLyXskip";
1143 $tex_mode_string .=&Verbatim::copy_verbatim($fileobject,$eaten);
1150 if (/^End::Group::Args$/) {
1151 print $eaten->print," " if $debug_on; # the string "\end{foo}"
1152 my $env = $eaten->environment;
1154 # End of list or quote/verse environment
1155 # OR special environment given with -t option
1156 if (exists $ReadCommands::ToLayout->{$env}) {
1158 $item_preface = ""; # reset when at end of List env.
1160 # End of math environments
1161 } elsif ($env =~ /^$MathEnvironments$/o) {
1162 print OUTFILE "\\end{$env}\n\\end_inset \n\n";
1163 print "\nDone copying math environment '$env'" if $debug_on;
1165 } elsif (exists $AlignEnvironments{$env}) {
1166 # Back to block alignment
1167 $CurrentAlignment = "";
1168 print "\nBack to block alignment" if $debug_on;
1170 # assume that \end should end a paragraph
1171 # This isn't correct LaTeX, but LyX can't align part of a par
1172 $IsNewParagraph = 1;
1174 # Environments lyx translates to floats
1175 } elsif (exists $FloatEnvTransTable{$env}) {
1176 print OUTFILE "\n\\end_float \n\n";
1179 } elsif ($env =~ /tabular$/) { # don't allow tabular*
1180 if ($thistable = &RelyxTable::in_table) {
1181 $thistable->done_reading;
1182 print OUTFILE "\n$RelyxTable::TableEndString\n";
1183 } else {warn "found \\end{tabular} when not in table!"}
1185 # Anything after a table will be a new paragraph
1186 $IsNewParagraph = 1; $MayBeDeeper = 1;
1189 } elsif ($env eq "minipage") {
1190 print OUTFILE "\n\\end_inset \n\n";
1192 # Next stuff will be new env.
1193 # $IsNewParagraph = 1;
1195 } elsif ($env eq "document") {
1196 print "\nDone with document!" if $debug_on;
1198 # "regular" environment given with -r option
1199 } elsif (grep /^\Q$env\E$/, @ReadCommands::regular_env) {
1200 $dummy = $eaten->print; # \end{env}, ignore initial whitespace
1201 &print_tex_mode($dummy);
1203 # Next stuff will be new env.
1204 $IsNewParagraph = 1;
1206 # End of unknown environments. We're already in TeX mode
1208 # Add \end{env} (including initial whitespace) to string
1209 # For reLyXskip environment, don't print \begin & \end commands!
1210 $tex_mode_string .= $eaten->exact_print
1211 unless $env eq "reLyXskip";
1213 &print_tex_mode($tex_mode_string);
1214 print "Done copying unknown environment '$env'" if $debug_on;
1221 # Note for text handling: we have to do lots of stuff to handle
1222 # spaces in (as close as possible to) the same way that LaTeX does
1223 # LaTeX considers all whitespace to be the same, so basically, we
1224 # convert each clump of whitespace to one space. Unfortunately, there
1225 # are special cases, like whitespace at the beginning/end of a par,
1226 # which we have to get rid of to avoid extra spaces in the LyX display.
1227 # \n at the end of a paragraph must be considered like a space,
1228 # because the next line might begin with a token like \LyX. But
1229 # if the next line starts with \begin, say, then an extra space will be
1230 # generated in the LyX file. Oh well. It doesn't affect the dvi file.
1232 my $outstr = $eaten->print; # the actual text
1234 # don't bother printing whitespace
1235 # Note: this avoids the problem of extra whitespace generating
1236 # extra Text::TeX::Paragraphs, which would generate extra
1238 last TYPESW if $outstr =~ /^\s+$/;
1240 # whitespace at beginning of a paragraph is meaningless
1241 # e.g. \begin{foo}\n hello \end{foo} shouldn't print the \n
1242 # (Note: check IsNewParagraph BEFORE calling &CFNP, which zeros it)
1243 my $replace = $IsNewParagraph ? "" : " ";
1244 $outstr =~ s/^\s+/$replace/;
1246 # Only write '\layout Standard' once per paragraph
1247 &CheckForNewParagraph;
1249 # \n\n signals end of paragraph, so get rid of it (and any space
1251 $outstr =~ s/\s*\n\n$//;
1253 # Print the LaTeX text to STDOUT
1254 print "'$outstr'" if $debug_on;
1256 # LyX *ignores* \n and \t, whereas LaTeX considers them just
1258 # Also, many spaces are equivalent to one space in LaTeX
1259 # (But LyX misleadingly displays them on screen, so get rid of them)
1260 $outstr =~ s/\s+/ /g;
1262 # protect spaces in an optional argument if necessary
1263 # Put a SPACE after the argument for List, Description layouts
1264 if ($protect_spaces) {
1265 $dummy = $TextTokenTransTable{'~'};
1267 # This will not handle brackets in braces!
1268 if ($outstr =~ /\]/) { # protect spaces only *until* the bracket
1271 # Note that any \t's have been changed to space already
1272 $tempstr =~ s/ /$dummy/g;
1274 # Print 1 space after the argument (which finished with ])
1275 # Don't print 2 (i.e. remove leading space from $tempstr2)
1276 # don't print the bracket
1277 $tempstr2 =~ s/^ //;
1278 $outstr = "$tempstr $tempstr2";
1279 $protect_spaces = 0; # Done with optional argument
1280 } else { # protect all spaces, since we're inside brackets
1281 $outstr =~ s/ /$dummy/g;
1283 } # end special stuff for protected spaces
1285 # Translate any LaTeX text that requires special LyX handling
1286 foreach $dummy (keys %TextTransTable) {
1287 $outstr =~ s/\Q$dummy\E/$TextTransTable{$dummy}/g;
1290 # "pretty-print" the string. It's not perfect, since there may
1291 # be text in the OUTFILE before $outstr, but it will keep from
1292 # having REALLY long lines.
1293 # Try to use approximately the same word-wrapping as LyX does:
1294 # - before space after a period, except at end of string
1295 # - before first space after column seventy-one
1296 # - after 80'th column
1298 $outstr =~ s/\. (?!$)/.\n / or
1299 $outstr =~ s/(.{71,79}?) /$1\n / or
1300 $outstr =~ s/(.{80})(.)/$1\n$2/ or
1301 last; # exit loop if string is < 79 characters
1304 # Actually print the text
1305 print OUTFILE "$outstr";
1307 } # end TT::Text handling
1309 # The default action - this should never happen!
1310 print("I don't know ",$eaten->print) if $debug_on;
1314 print "\n" if $debug_on;
1316 } #end sub basic_lyx
1318 ######################### TEX MODE SUBROUTINES #########################
1320 # This subroutine copies and prints a latex token and its arguments if any.
1321 # This sub is only needed if the command was not found in the syntax file
1322 # Use exact_print to preserve, e.g., whitespace after macros
1323 sub copy_latex_unknown {
1325 my $fileobject = shift;
1326 my $outstr = $eaten->exact_print;
1327 my ($dummy, $tok, $count);
1329 # Copy the actual word. Copy while you've still got
1330 # arguments. Assume all args must be in the same paragraph
1331 # (There could be new paragraphs *within* args)
1332 # We can't use copy_verbatim (unless we make it smarter) because
1333 # it would choke on nested braces
1334 print "\nUnknown token: '",$eaten->print,"': Copying in TeX mode\n"
1337 while (($dum2 = $fileobject->lookAheadToken) &&
1338 ($dum2 =~ /^[[{]$/)) {
1339 if ($dum2 eq '[') { #copy optional argument - assume it's simple
1340 $tok = $fileobject->eatOptionalArgument;
1341 $outstr .= $tok->exact_print; # also print brackets & whitespace
1344 EAT: { #copied from eatBalanced, but allow paragraphs
1345 die unless defined ($tok = $fileobject->eatMultiToken);
1346 $outstr.="\n",redo EAT if ref($tok) eq "Text::TeX::Paragraph";
1347 $dummy = $tok->exact_print;
1349 # Sometimes, token will be '\n{', e.g.
1350 $count++ if $dummy =~ /^\s*\{$/; # add a layer of nesting
1351 $count-- if $dummy =~ /^\s*\}$/; # end one layer of nesting
1352 redo EAT if $count; #don't dump out until all done nesting
1354 } # end if $dummy = [{
1357 # Add {} after macro if it's followed by '}'. Otherwise, {\foo}bar
1358 # will yield \foobar when LyX creates LaTeX files
1359 $outstr.="{}" if $outstr=~/\\[a-zA-Z]+$/ && $dum2 eq '}';
1361 # Print it out in TeX mode
1362 &print_tex_mode($outstr);
1364 print "\nDone copying unknown token" if $debug_on;
1365 } # end sub copy_latex_unknown
1367 # Copy an untranslatable latex command whose syntax we know, along with its
1369 # The command itself, optional arguments, and untranslatable
1370 # arguments get copied in TeX mode. However, arguments which contain regular
1371 # LaTeX will get translated by reLyX. Do that by printing what you have so
1372 # far in TeX mode, leaving this subroutine, continuing with regular reLyX
1373 # translating, and then returning here when you reach the ArgToken or
1374 # EndArgsToken at the end of the translatable argument.
1375 # We need to keep a stack of the tokens that brought us here, because
1376 # you might have nested commands (e.g., \mbox{hello \fbox{there} how are you}
1377 sub copy_latex_known {
1378 my ($eaten, $fileobject) = (shift,shift);
1379 my $type = ref($eaten);
1380 $type =~ s/^Text::TeX::// or die "unknown token?!";
1382 # token itself for TT::Token, TT::BegArgsToken,
1383 # Corresponding BegArgsToken for ArgToken,EndArgsToken
1384 my $temp_start = $eaten->base_token;
1386 # Initialize tex mode copying
1387 if ($type eq "BegArgsToken" or $type eq "Token") {
1388 print "\nCopying untranslatable token '",$eaten->print,
1389 "' in TeX mode" if $debug_on;
1390 push @tex_mode_tokens, $temp_start;
1392 # initialize the string of stuff we're copying
1393 $tex_mode_string = $eaten->exact_print;
1394 } # Start tex copying?
1397 # This token's next arguments -- returns a string matching /o*[rR]?/
1398 my $curr_args = $eaten->next_args($fileobject);
1400 if ($type eq "EndArgsToken" or $type eq "ArgToken") {
1401 # Print ending '}' for the group we just finished reading
1402 $tex_mode_string .= '}';
1405 # If there could be optional arguments next, copy them
1406 while ($curr_args =~ s/^o// && $fileobject->lookAheadToken eq '[') {
1407 my $opt = $fileobject->eatOptionalArgument;
1408 $tex_mode_string .= $opt->exact_print;
1410 $curr_args =~ s/^o*//; # Some OptArgs may not have appeared
1412 if ($type eq "BegArgsToken" or $type eq "ArgToken") {
1413 # Print beginning '{' for the group we're about to read
1414 $tex_mode_string .= '{';
1417 # Now copy the next required argument, if any
1418 # Copy it verbatim (r), or translate it as regular LaTeX (R)?
1419 if ($curr_args =~ s/^r//) {
1420 my $group = $fileobject->eatRequiredArgument;
1421 my $temp = $group->exact_print;
1422 # Remove braces. They're put in explicitly
1423 $temp =~ s/\{(.*)\}/$1/; # .* is greedy
1424 $tex_mode_string .= $temp;
1426 } elsif ($curr_args =~ s/^R//) {
1427 print "\n" if $debug_on;
1428 &print_tex_mode($tex_mode_string);
1429 $tex_mode_string = "";
1430 print "\nTranslating this argument for ",$temp_start->print,
1431 " as regular LaTeX" if $debug_on;
1433 } else { # anything but '' is weird
1434 warn "weird arg $curr_args to ",$temp_start->print,"\n" if $curr_args;
1437 # Finished tex mode copying
1438 if ($type eq "Token" or $type eq "EndArgsToken") {
1440 # Add {} to plain tokens followed by { or }. Otherwise {\foo}bar
1441 # and \foo{bar} yield \foobar in the LaTeX files created by LyX
1443 if ($type eq "Token" and
1444 $dummy=$fileobject->lookAheadToken and
1447 $tex_mode_string .= '{}';
1450 # Print out the string
1451 print "\n" if $debug_on;
1452 &print_tex_mode($tex_mode_string);
1453 $tex_mode_string = "";
1455 # We're done with this token
1456 pop(@tex_mode_tokens);
1458 my $i = $type eq "Token" ? "" : " and its arguments";
1459 my $j = $temp_start->print;
1460 print "\nDone copying untranslatable token '$j'$i in TeX mode"
1462 } # end tex copying?
1463 } # end sub copy_latex_known
1465 # Print a string in LyX "TeX mode"
1466 # The goal of this subroutine is to create a block of LyX which will be
1467 # translated exactly back to the original when LyX creates its temporary LaTeX
1468 # file prior to creating a dvi file.
1469 # Don't print \n\n at the end of the string... instead just set the new
1470 # paragraph flag. Also, don't print whitespace at the beginning of the string.
1471 # Print nothing if it's the beginning of a paragraph, or space otherwise.
1472 # These two things avoid extra C-Enter's in the LyX file
1473 sub print_tex_mode {
1476 print "'$outstr'" if $debug_on;
1478 # Handle extra \n's (& spaces) at beginning & end of string
1479 my $str_ends_par = ($outstr =~ s/\n{2,}$//);
1480 if ($IsNewParagraph) {
1481 $outstr =~ s/^\s+//; # .e.g, $outstr follows '\begin{quote}'
1483 # Any whitespace is equivalent to one space in LaTeX
1484 $outstr =~ s/^\s+/ /; # e.g. $outstr follows "\LaTeX{}" or "Hi{}"
1487 # Check whether string came right at the beginning of a new paragraph
1488 # This *might* not be necessary. Probably can't hurt.
1489 &CheckForNewParagraph;
1492 print OUTFILE "$start_tex_mode";
1494 # Do TeX mode translation;
1495 $outstr =~ s/\\/\n\\backslash /g;
1496 # don't translate \n in '\n\backslash' that we just made!
1497 $outstr =~ s/\n(?!\\backslash)/\n\\newline \n/g;
1499 # Print the actual token + arguments if any
1500 print OUTFILE $outstr;
1502 # Get OUT of LaTeX mode (and end nesting if nec.)
1503 print OUTFILE "$end_tex_mode";
1504 $IsNewParagraph = $str_ends_par;
1507 } # end sub print_tex_mode
1509 ############################ LAYOUT SUBROUTINES ###########################
1511 sub CheckForNewParagraph {
1512 # This subroutine makes sure we only write \layout command once per paragraph
1513 # It's mostly necessary cuz 'Standard' layout is the default in LaTeX;
1514 # there is no command which officially starts a standard environment
1515 # If we're in a table, new layouts aren't allowed, so just return
1516 # If $IsNewParagraph is 0, it does nothing
1517 # If $INP==1, It starts a new paragraph
1518 # If $CurrentAlignment is set, it prints the alignment command for this par.
1519 # If $MayBeDeeper==1 and we're currently within a list environment,
1520 # it starts a "deeper" Standard paragraph
1522 my $layout = $$CurrentLayoutStack[-1];
1524 return if &RelyxTable::in_table;
1526 if ($IsNewParagraph) {
1527 # Handle standard text within a list environment specially
1529 if ($layout =~ /^$ListLayouts$/o) {
1530 push (@$CurrentLayoutStack, "Standard");
1531 print "\nCurrent Layout Stack: @$CurrentLayoutStack\n"
1533 print OUTFILE "\n\\begin_deeper ";
1534 $layout = "Standard";
1536 $MayBeDeeper = 0; # Don't test again until new paragraph
1539 # Print layout command itself
1540 print OUTFILE "\n\\layout $layout\n\n";
1541 print OUTFILE $CurrentAlignment if $CurrentAlignment;
1543 # Now that we've written the command, it's no longer a new paragraph
1544 $IsNewParagraph = 0;
1546 # And we're no longer waiting to see if this paragraph is empty
1549 # When you write a new paragraph, reprint any font commands
1550 foreach $dummy (keys %$CurrentFontStatus) {
1551 my $currf = $CurrentFontStatus->{$dummy}->[-1];
1552 if ($currf ne "default") {
1553 print OUTFILE "\n$dummy $currf \n";
1557 } # end sub CheckForNewParagraph
1559 sub ConvertToLayout {
1560 # This subroutine begins a new layout, pushing onto the layout stack, nesting
1561 # if necessary. It doesn't actually write the \layout command -- that's
1562 # done by CheckForNewParagraph.
1563 # The subroutine assumes that it's being passed an environment name or macro
1564 # which is known and which creates a known layout
1565 # It uses the ToLayout hash (created by the ReadCommands module) which
1566 # gives the LyX layout for a given LaTeX command or environment
1567 # Arg0 is the environment or macro
1570 my $layoutref = $ReadCommands::ToLayout->{$name};
1571 my $layout = $layoutref->{'layout'};
1572 my $keepempty = $layoutref->{'keepempty'};
1573 my $dummy = ($name =~ /^\\/ ? "macro" : "environment");
1574 print "\nChanging $dummy $name to layout $layout" if $debug_on;
1576 # Nest if the layout stack has more than just "Standard" in it
1577 if ($#{$CurrentLayoutStack} > 0) {
1578 # Die here for sections & things that can't be nested!
1579 print " Nesting!" if $debug_on;
1580 print OUTFILE "\n\\begin_deeper ";
1583 # If we still haven't printed the *previous* \layout command because that
1584 # environment is empty, print it now! (This happens if an environment
1585 # is nested inside a keepempty, like slide.)
1586 &CheckForNewParagraph if $PendingLayout;
1588 # Put the new layout onto the layout stack
1589 push @$CurrentLayoutStack, $layout;
1590 print "\nCurrent Layout Stack: @$CurrentLayoutStack" if $debug_on;
1592 # Upcoming text will be new paragraph, needing a new layout cmd
1593 $IsNewParagraph = 1;
1595 # Test for nested "Standard" paragraph in upcoming text?
1596 # Some environments can nest. Sections & Title commands can't
1597 $MayBeDeeper = $layoutref->{"nestable"};
1599 # We haven't yet read any printable stuff in the new paragraph
1600 # If this is a layout that's allowed to be empty, prepare for an
1602 $PendingLayout = $keepempty;
1604 } # end sub ConvertToLayout
1607 # This subroutine ends a layout, popping the layout stack, etc.
1608 # The subroutine assumes that it's being passed an environment name or macro
1609 # which is known and which creates a known layout
1610 # It uses the ToLayout hash (created by the ReadCommands module) which
1611 # gives the LyX layout for a given LaTeX command or environment
1612 # Arg0 is the environment or macro
1615 my $layoutref = $ReadCommands::ToLayout->{$name};
1616 my $layout = $layoutref->{'layout'};
1617 my $dummy = ($name =~ /^\\/ ? "macro" : "environment");
1618 print "\nEnding $dummy $name (layout $layout)" if $debug_on;
1620 # If we still haven't printed the *previous* \layout command because that
1621 # environment is empty, print it now! Before we pop the stack!
1622 # This happens for a totally empty, non-nesting environment,
1623 # like hollywood.sty's fadein
1624 &CheckForNewParagraph if $PendingLayout;
1626 my $mylayout = pop (@$CurrentLayoutStack);
1628 # If a standard paragraph was the last thing in a list, then
1629 # we need to end_deeper and then pop the actual list layout
1630 # This should only happen if the Standard layout was nested
1631 # in a nestable layout. We don't check.
1632 if ($mylayout eq "Standard") {
1633 print OUTFILE "\n\\end_deeper ";
1634 print " End Standard Nesting!" if $debug_on;
1635 $mylayout = pop (@$CurrentLayoutStack);
1638 # The layout we popped off the stack had better match the
1639 # environment (or macro) we're ending!
1640 if ($mylayout ne $layout) { die "Problem with Layout Stack!\n"};
1641 print "\nCurrent Layout Stack: @$CurrentLayoutStack" if $debug_on;
1643 # If we're finishing a nested layout, we need to end_deeper
1644 # This should only happen if the layout was nested
1645 # in a nestable layout. We don't check.
1646 # Note that if we're nested in a list environment and the
1647 # NEXT paragraph is Standard, then we'll have an extra
1648 # \end_deeper \begin_deeper in the LyX file. It's sloppy
1649 # but it works, and LyX will get rid of it when it
1651 if ($#{$CurrentLayoutStack} > 0) {
1652 print " End Nesting!" if $debug_on;
1653 print OUTFILE "\n\\end_deeper ";
1656 # Upcoming text will be new paragraph, needing a new layout cmd
1657 $IsNewParagraph = 1;
1659 # Test for nested "Standard" paragraph in upcoming text?
1660 # Some environments can nest. Sections & Title commands can't
1661 $MayBeDeeper = $layoutref->{"nestable"};
1662 } # end sub EndLayout
1664 ####################### MISCELLANEOUS SUBROUTINES ###########################
1666 # Translate math commands LyX doesn't support into commands it does support
1671 (.*?) # non-token matter ($1)
1672 (\\ # token ($2) is a backslash followed by ...
1673 ( ([^A-Za-z] \*?) | # non-letter (and *) ($4) OR
1674 ([A-Za-z]+ \*?) ) # letters (and *) ($5)
1675 )//xs) # /x to allow whitespace/comments, /s to copy \n's
1679 if (exists $ReadCommands::math_trans{$tok}) {
1680 $tok = $ReadCommands::math_trans{$tok};
1681 # add ' ' in case we had, e.g., \|a, which would become \Verta
1682 # Only need to do it in those special cases
1684 defined $4 && $tok =~ /[A-Za-z]$/ && $input =~ /^[A-Za-z]/;
1688 $output .= $input; # copy what's left in $input
1693 1; # return true to calling subroutine