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 # Read a file containing LaTeX commands and their syntax
8 # Also, read a file containing LyX layouts and their LaTeX equivalents
12 # Variables needed by other modules
13 # ToLayout is a ref to a hash which contains LyX layouts & LaTeX equivalents
14 # regular_env is a list of environments that have "reLyXable" LaTeX in them
15 # math_trans is a hash of math commands and what they're translated to
16 use vars qw($ToLayout @regular_env %math_trans);
18 # CommandHash is the hash of LaTeX commands and their syntaxes which
19 # this package builds.
22 # Name of the environment containing names of regular environments :)
23 my $regenv_name = "reLyXre";
24 # Name of environment containing translations of math commands
25 my $math_trans_name = "reLyXmt";
26 # Variables set when in the above environments
27 my ($in_regular_env, $in_math_trans);
28 my @Environments = qw($regenv_name $math_trans_name);
30 # This word in a command's argument (in the syntax file) means that reLyX
31 # should translate that argument as regular LaTeX
32 my $Translate_Word = "translate";
34 ######################### READ COMMAND SYNTAX ################################
35 sub read_syntax_files {
36 # This subroutine calls the TeX parser & translator to read LaTeX syntax file(s)
37 # It sets the list of "regular" environments, environments which aren't known
38 # by reLyX, but which contain text that reLyX can translate.
39 # It also reads math commands which should be translated (e.g., \sp -> ^)
41 # @_ contains the syntax file(s) to read
45 # Before anything else, set the package-wide variables based on the
47 # opt_d is set to 1 if '-d' option given, else (probably) undefined
48 my $debug_on = (defined($main::opt_d) && $main::opt_d);
50 # opt_r is a group of environments to copy like regular text
51 # If opt_r wasn't given, then there are no such special environments
52 @regular_env = (defined $main::opt_r ? (split(/,/, $main::opt_r)) : () );
54 # The only important commands to pass are \begin and \end, so that the
55 # parser realizes they start/end environments, as opposed to being
57 my %MyTokens = ( '{' => $Text::TeX::Tokens{'{'},
58 '}' => $Text::TeX::Tokens{'}'},
59 '\begin' => $Text::TeX::Tokens{'\begin'},
60 '\end' => $Text::TeX::Tokens{'\end'},
64 foreach $InFileName (@syntaxfiles) {
65 die "could not find syntax file $InFileName" unless -e $InFileName;
66 my $zzz=$debug_on ? "from $InFileName " :"";
67 warn "Reading LaTeX command syntax $zzz\n";
69 # Open the file to turn into LyX.
70 my $infile = new Text::TeX::OpenFile $InFileName,
71 'defaultact' => \&read_commands,
72 'tokens' => \%MyTokens;
74 # When we start (each file), we're not reading regular environments yet
82 print "Regular environments: @regular_env\n";
83 my @mathkeys = keys(%math_trans);
84 print " Math command | translation\n" if @mathkeys;
85 foreach (@mathkeys) { printf("%20s %s\n",$_,$math_trans{$_}) }
88 #warn "Done reading commands\n";
90 } # end subroutine call_parser
93 # This subroutine is called by Text::TeX::process
94 # Arg0 is the token we just ate
95 # Arg1 is the file object we're reading from
97 # We create a hash, where each command is a key. The value is just a string
98 # of zero or more 'o' and 'r' characters. Each 'o' stands for an optional
99 # argument, 'r' stands for a required argument. 'R' stands for a required
100 # argument whose text will be regular LaTeX, e.g., the argument to \mbox{}
101 # In addition, the $regenv_name environment contains
102 # regular environments, like those input with the -r option.
103 # Note that if a command is found more than once, then it wil be overwritten.
104 # This is a feature. This way, a user-defined syntax file can overwrite the
105 # argument list found in the default syntax file.
106 my ($token,$fileobject) = (shift,shift);
108 my $type = ref($token);
109 $type =~ s/^Text::TeX::// or die "unknown token type $type from Text::TeX";
110 #print $token->exact_print, unless $type eq "Paragraph";
111 #print $token->comment if $token->comment;
113 # Because there's no token list, ALL tokens will be
114 # Paragraph, Text, or Token
115 SWITCH: for ($type) {
116 # Handle blank lines.
122 # Comment in its own paragraph... skip
123 last SWITCH unless defined($token->print);
125 if ($in_math_trans) { # read translations of math commands
126 my $key = $token->print;
127 # Translation is whatever's in the argument to the token
128 # (There might be multiple tokens in there)
129 my @vals = $fileobject->eatBalanced->contents;
130 my $val = join ("", map {$_->exact_print} @vals);
131 $math_trans{$key} = $val;
133 } else { # regular portion of syntax file
136 # read while there are arguments
137 while (($dum2 = $fileobject->lookAheadToken) &&
138 ($dum2 =~ /^[[{]$/)) {
139 if ($dum2 eq '[') { #eat optional argument - assumed simple
140 $fileobject->eatOptionalArgument;
143 my $tok = $fileobject->eatBalanced or warn "bad group";
144 if ($tok->exact_print eq $Translate_Word) {
149 } # end if $dummy = [{
150 } # done reading command
151 $CommandHash{$token->print} = $args;
152 } # in math trans env or regular token?
156 } elsif (/^Begin::Group::Args/) {
157 my $env = $token->environment;
159 $in_regular_env = 1, last CASE if $env eq $regenv_name;
160 $in_math_trans = 1, last CASE if $env eq $math_trans_name;
161 warn "Unknown environment $env in syntax file";
164 } elsif (/^End::Group::Args/) {
165 my $env = $token->environment;
167 $in_regular_env = 0, last CASE if $env eq $regenv_name;
168 $in_math_trans = 0, last CASE if $env eq $math_trans_name;
169 warn "Unknown environment $env in syntax file";
173 # don't do anything unless we're reading environments
174 if ($in_regular_env) {
175 my @new_envs = (split(/\s+/, $token->print));
176 @new_envs = grep ($_, @new_envs); # remove empty elements
177 push @regular_env,@new_envs;
181 die "unexpected token type $type";
186 } # end sub read_commands
189 # This sub creates a token list (which could be used to call a Text::TeX
190 # parser) from %CommandHash, and merges it with the input token list
191 # If a command takes any required arguments, it will be a report_args,
192 # but if it just takes an optional argument, it can stay a regular old token.
193 # In either case, we insert a new field, "relyx_args", into the token list,
194 # which is the expected order of arguments for that command. Even if there
195 # are no args, we insert an empty relyx_args, so that we can differentiate
196 # between a truly unknown token and a known token which takes no args.
198 # We don't allow a new command to override a command that already exists in
199 # OldHash, i.e., one that was defined explicitly in the calling sub.
201 # Arg0 is a (reference to an) existing token list
202 my $OldHashRef = shift;
204 foreach (keys %CommandHash) {
205 my $val = $CommandHash{$_};
206 my ($entry, $count, @foo);
207 if (!exists $OldHashRef->{$_}) {
208 if ($count = scalar(@foo = ($val =~ /r/gi))) {
209 # TeX.pm will make this a TT::BegArgsToken and $count-1
210 # TT::ArgTokens, followed by a TT::EndArgsToken
211 $OldHashRef->{$_} = {"Type" => 'report_args',
213 "relyx_args" => $val};
214 } else { # only non-required args
215 # Make it a regular TT::Token, but write relyx_args
216 # (even if $val is "")
217 $OldHashRef->{$_} = {"relyx_args" => $val};
224 ############################ READ LAYOUTS ####################################
225 sub read_layout_files {
226 # This subroutine reads a textclass-specific layout file and all files
227 # included in that file.
228 # It sets up the layout hash table. For each environment, it describes which
229 # layout that environment refers to. It does the same for macros which
230 # begin LyX layouts (e.g., \section)
231 # If we read a command that's not already in CommandHash, it means that this
232 # layout has some commands that aren't in syntax.default. If so, we ASSUME
233 # that the command takes just one required argument, and put it in
234 # CommandHash, so that &Merge will eventually put these commands into the
237 # TODO: we actually need to allow more sophisticated stuff. E.g. \foilhead
238 # is converted to Foilhead or ShortFoilHead (foils.layout) depending on whether
239 # the command has "r" or "or" arguments. Reading LatexParam (if it exists)
240 # can help us with this.
241 # Default is "r". Just unshift other args as you read them, since latexparam
242 # is put in between macro & the argument
243 # TODO: We need to store ToLayout s.t. we can have > 1 layout per command.
244 # Maybe by default just have one layout, but if (ref(layout)) then read
245 # more args & thereby figure out which layout?
247 # Arg0 is the name of the documentclass
250 my $doc_class = shift;
254 # ToLayout{latexname} stores layout; so ReversHash{layout} = latexname
256 my $debug_on = (defined($main::opt_d) && $main::opt_d);
258 # look for layout file in $HOME/.lyx first, then system layouts directory
259 my $searchname = "$doc_class.layout";
261 my $personal_layout = "$main::dot_lyxdir/layouts";
262 push(@searchdirs,$personal_layout) if -e $personal_layout;
263 my $system_layout = "$main::lyxdir/layouts";
264 # I guess this won't exist if running reLyX without installing...
265 # Of course, in that case, this will probably break
266 push(@searchdirs,$system_layout) if -e $system_layout;
267 my @foundfiles = grep(-e "$_/$searchname", @searchdirs) or
268 die "Cannot find layout file $searchname in dir(s) @searchdirs";
269 my $LayoutFileName = "$foundfiles[0]/$searchname"; # take first one we found
271 $fh = new FileHandle;
272 $fh->open ("<$LayoutFileName");
273 my $zzz=$debug_on ? "$LayoutFileName" :"";
274 warn "Reading layout file $zzz\n";
275 push @filestack, $fh;
277 # Read the layout file!
278 my ($lyxname, $latexname, $latextype, $latexparam, $keepempty);
281 # Read a line. If eof, pop the filestack to return to the file
282 # that included this file *or* finish if the stack's empty
283 unless (defined ($line = <$fh>)) {
286 last unless ($#filestack+1); # finish when stack is empty
287 $fh = $filestack[-1];
288 next; # read another line from the "calling" file
292 next if $line =~ /^\s*$/;
294 # Split the line. Use limit 2 since there may be whitespace in 2nd term
295 my ($field_name, $field_stuff) = split(' ', $line, 2);
296 $field_name = lc($field_name); # LyX is case insensitive for fields
297 if (defined($field_stuff)) {
298 $field_stuff =~ s/^\"(.*)\"/$1/;
299 chomp ($field_stuff);
300 # Since split is limited to 2 fields, there may be extra whitespace
301 # at end. LyX breaks on a "\layout Abstract " command!
302 $field_stuff =~ s/\s*$//;
305 # This set of ifs deals with lines outside a style definition
306 if ($field_name eq "style") { # start a style definition
307 $lyxname = $field_stuff;
308 # Styles in LyX have spaces, but _ in layout files
310 $latexname = ""; # make sure these variables are unset
314 } elsif ($field_name eq "input") { #include a file
315 $searchname = $field_stuff;
316 @foundfiles = grep(-e "$_/$searchname", @searchdirs) or
317 die "Cannot find layout file $searchname in dir(s) @searchdirs";
318 $fname = "$foundfiles[0]/$searchname"; # take first one we found
319 $fh = new FileHandle;
320 push @filestack, $fh;
321 $fh->open("<$fname");
322 print "Reading included layout file $fname\n" if $debug_on;
325 next unless $lyxname; # not w/in style definition
327 # This set of ifs deals with lines within a Style definition
328 if ($field_name eq "latexname") {
329 $latexname = $field_stuff;
331 } elsif ($field_name eq "latexparam") {
332 #$dum = $field_stuff;
333 $latexparam = $field_stuff;
335 } elsif ($field_name eq "latextype") {
336 $latextype = $field_stuff;
338 } elsif ($field_name eq "keepempty") {
339 $keepempty = $field_stuff;
341 } elsif ($field_name eq "copystyle") { # copy an existing style
342 # "if" is necessary in case someone tries "CopyStyle Standard"
343 if (exists $ReverseHash{$field_stuff}) {
344 my $layref = $ReverseHash{$field_stuff};
345 $latexname = $layref->{"name"};
346 $latextype = $layref->{"type"};
347 $latexparam = $layref->{"param"};
348 $keepempty = $layref->{"keepempty"};
351 # When you get to the end of a definition, create hash table entries
352 # (if you've found the right information)
353 } elsif ($field_name eq "end") {
355 if ($latextype and $latexname) {
356 # Create reverse hash entry (needed for CopyStyle)
357 # Do it before making modifications to $latexname, e.g.
358 $ReverseHash{$lyxname} = {"name" => $latexname,
359 "type" => $latextype,
360 "param" => $latexparam,
361 "keepempty" => $keepempty,
364 my ($nest, $skip) = (0,0);
365 for ($latextype) { # make $_=$latextype
367 # Macros need a '\' before them. Environments don't
368 $latexname = '\\' . $latexname;
370 # Create the command if it wasn't in syntax.default
371 unless (exists $CommandHash{$latexname}) {
372 $CommandHash{$latexname} = "r";
375 } elsif (/^Environment/) {
377 } elsif (/Item_Environment/i || /List_Environment/) {
380 # layout Standard has LatexType Paragraph. It shouldn't
381 # have any hash entry at all
382 } elsif (/^Paragraph$/) {
385 warn "unknown LatexType $latextype" .
386 "for $latexname (layout $lyxname)!\n";
388 # Handle latexparam, if any
389 # if ($dum =~ s/^"(.*)"/$1/) { # newer layout file syntax
390 # while ($dum =~ /^[[{]/) {
391 # $dum =~ s/\[.*?\]// && ($latexargs = "o$latexargs") or
392 # $dum =~ s/\{.*?\}// && ($latexargs = "r$latexargs");
394 # warn "leftover LatexParam stuff $dum" if $dum;
396 # if ($latextype eq "Command") {optarg}
401 # Create the hash entry
403 $ToLayout->{$latexname} = {"layout" => $lyxname,
405 "keepempty" => $keepempty};
408 # Now that we've finished the style, unset $lyxname so that
409 # we'll skip lines until the next style definition
411 } # end if ($latextype and $latexname)
416 ## Print every known layout
417 # print " LatexName Layout Keepempty?\n";
418 # foreach (sort keys %$ToLayout) {
419 # printf "%20s%15s %1d\n",$_,$ToLayout->{$_}{'layout'},
420 # $ToLayout->{$_}{'keepempty'};
422 #warn "Done reading layout files\n";
424 } # end sub read_layout_files
428 1; # return TRUE to calling routine