mirror of
https://git.lyx.org/repos/lyx.git
synced 2024-11-30 05:12:40 +00:00
27de1486ca
git-svn-id: svn://svn.lyx.org/lyx/lyx-devel/trunk@140 a592a061-630c-0410-9148-cb99ea01b6c8
429 lines
15 KiB
Perl
429 lines
15 KiB
Perl
# This file is part of reLyX
|
|
# Copyright (c) 1998-9 Amir Karger karger@post.harvard.edu
|
|
# You are free to use and modify this code under the terms of
|
|
# the GNU General Public Licence version 2 or later.
|
|
|
|
package ReadCommands;
|
|
# Read a file containing LaTeX commands and their syntax
|
|
# Also, read a file containing LyX layouts and their LaTeX equivalents
|
|
|
|
use strict;
|
|
|
|
# Variables needed by other modules
|
|
# ToLayout is a ref to a hash which contains LyX layouts & LaTeX equivalents
|
|
# regular_env is a list of environments that have "reLyXable" LaTeX in them
|
|
# math_trans is a hash of math commands and what they're translated to
|
|
use vars qw($ToLayout @regular_env %math_trans);
|
|
|
|
# CommandHash is the hash of LaTeX commands and their syntaxes which
|
|
# this package builds.
|
|
my %CommandHash;
|
|
|
|
# Name of the environment containing names of regular environments :)
|
|
my $regenv_name = "reLyXre";
|
|
# Name of environment containing translations of math commands
|
|
my $math_trans_name = "reLyXmt";
|
|
# Variables set when in the above environments
|
|
my ($in_regular_env, $in_math_trans);
|
|
my @Environments = qw($regenv_name $math_trans_name);
|
|
|
|
# This word in a command's argument (in the syntax file) means that reLyX
|
|
# should translate that argument as regular LaTeX
|
|
my $Translate_Word = "translate";
|
|
|
|
######################### READ COMMAND SYNTAX ################################
|
|
sub read_syntax_files {
|
|
# This subroutine calls the TeX parser & translator to read LaTeX syntax file(s)
|
|
# It sets the list of "regular" environments, environments which aren't known
|
|
# by reLyX, but which contain text that reLyX can translate.
|
|
# It also reads math commands which should be translated (e.g., \sp -> ^)
|
|
#
|
|
# @_ contains the syntax file(s) to read
|
|
|
|
my @syntaxfiles = @_;
|
|
|
|
# Before anything else, set the package-wide variables based on the
|
|
# user-given flags
|
|
# opt_d is set to 1 if '-d' option given, else (probably) undefined
|
|
my $debug_on = (defined($main::opt_d) && $main::opt_d);
|
|
|
|
# opt_r is a group of environments to copy like regular text
|
|
# If opt_r wasn't given, then there are no such special environments
|
|
@regular_env = (defined $main::opt_r ? (split(/,/, $main::opt_r)) : () );
|
|
|
|
# The only important commands to pass are \begin and \end, so that the
|
|
# parser realizes they start/end environments, as opposed to being
|
|
# regular old tokens.
|
|
my %MyTokens = ( '{' => $Text::TeX::Tokens{'{'},
|
|
'}' => $Text::TeX::Tokens{'}'},
|
|
'\begin' => $Text::TeX::Tokens{'\begin'},
|
|
'\end' => $Text::TeX::Tokens{'\end'},
|
|
);
|
|
|
|
my $InFileName;
|
|
foreach $InFileName (@syntaxfiles) {
|
|
die "could not find syntax file $InFileName" unless -e $InFileName;
|
|
my $zzz=$debug_on ? "from $InFileName " :"";
|
|
warn "Reading LaTeX command syntax $zzz\n";
|
|
|
|
# Open the file to turn into LyX.
|
|
my $infile = new Text::TeX::OpenFile $InFileName,
|
|
'defaultact' => \&read_commands,
|
|
'tokens' => \%MyTokens;
|
|
|
|
# When we start (each file), we're not reading regular environments yet
|
|
$in_regular_env = 0;
|
|
|
|
# Process the file
|
|
$infile->process;
|
|
}
|
|
|
|
if ($debug_on) {
|
|
print "Regular environments: @regular_env\n";
|
|
my @mathkeys = keys(%math_trans);
|
|
print " Math command | translation\n" if @mathkeys;
|
|
foreach (@mathkeys) { printf("%20s %s\n",$_,$math_trans{$_}) }
|
|
}
|
|
|
|
#warn "Done reading commands\n";
|
|
return;
|
|
} # end subroutine call_parser
|
|
|
|
sub read_commands {
|
|
# This subroutine is called by Text::TeX::process
|
|
# Arg0 is the token we just ate
|
|
# Arg1 is the file object we're reading from
|
|
#
|
|
# We create a hash, where each command is a key. The value is just a string
|
|
# of zero or more 'o' and 'r' characters. Each 'o' stands for an optional
|
|
# argument, 'r' stands for a required argument. 'R' stands for a required
|
|
# argument whose text will be regular LaTeX, e.g., the argument to \mbox{}
|
|
# In addition, the $regenv_name environment contains
|
|
# regular environments, like those input with the -r option.
|
|
# Note that if a command is found more than once, then it wil be overwritten.
|
|
# This is a feature. This way, a user-defined syntax file can overwrite the
|
|
# argument list found in the default syntax file.
|
|
my ($token,$fileobject) = (shift,shift);
|
|
|
|
my $type = ref($token);
|
|
$type =~ s/^Text::TeX::// or die "unknown token type $type from Text::TeX";
|
|
#print $token->exact_print, unless $type eq "Paragraph";
|
|
#print $token->comment if $token->comment;
|
|
|
|
# Because there's no token list, ALL tokens will be
|
|
# Paragraph, Text, or Token
|
|
SWITCH: for ($type) {
|
|
# Handle blank lines.
|
|
if (/Paragraph/) {
|
|
# don't do anything
|
|
last SWITCH;
|
|
|
|
} elsif (/^Token/) {
|
|
# Comment in its own paragraph... skip
|
|
last SWITCH unless defined($token->print);
|
|
|
|
if ($in_math_trans) { # read translations of math commands
|
|
my $key = $token->print;
|
|
# Translation is whatever's in the argument to the token
|
|
# (There might be multiple tokens in there)
|
|
my @vals = $fileobject->eatBalanced->contents;
|
|
my $val = join ("", map {$_->exact_print} @vals);
|
|
$math_trans{$key} = $val;
|
|
|
|
} else { # regular portion of syntax file
|
|
my ($dum2);
|
|
my $args = "";
|
|
# read while there are arguments
|
|
while (($dum2 = $fileobject->lookAheadToken) &&
|
|
($dum2 =~ /^[[{]$/)) {
|
|
if ($dum2 eq '[') { #eat optional argument - assumed simple
|
|
$fileobject->eatOptionalArgument;
|
|
$args .= "o";
|
|
} else {
|
|
my $tok = $fileobject->eatBalanced or warn "bad group";
|
|
if ($tok->exact_print eq $Translate_Word) {
|
|
$args .= "R";
|
|
} else {
|
|
$args .= "r";
|
|
}
|
|
} # end if $dummy = [{
|
|
} # done reading command
|
|
$CommandHash{$token->print} = $args;
|
|
} # in math trans env or regular token?
|
|
|
|
last SWITCH;
|
|
|
|
} elsif (/^Begin::Group::Args/) {
|
|
my $env = $token->environment;
|
|
CASE: {
|
|
$in_regular_env = 1, last CASE if $env eq $regenv_name;
|
|
$in_math_trans = 1, last CASE if $env eq $math_trans_name;
|
|
warn "Unknown environment $env in syntax file";
|
|
}
|
|
|
|
} elsif (/^End::Group::Args/) {
|
|
my $env = $token->environment;
|
|
CASE: {
|
|
$in_regular_env = 0, last CASE if $env eq $regenv_name;
|
|
$in_math_trans = 0, last CASE if $env eq $math_trans_name;
|
|
warn "Unknown environment $env in syntax file";
|
|
}
|
|
|
|
} elsif (/^Text/) {
|
|
# don't do anything unless we're reading environments
|
|
if ($in_regular_env) {
|
|
my @new_envs = (split(/\s+/, $token->print));
|
|
@new_envs = grep ($_, @new_envs); # remove empty elements
|
|
push @regular_env,@new_envs;
|
|
}
|
|
last SWITCH;
|
|
} else {
|
|
die "unexpected token type $type";
|
|
}
|
|
|
|
} # end SWITCH
|
|
|
|
} # end sub read_commands
|
|
|
|
sub Merge {
|
|
# This sub creates a token list (which could be used to call a Text::TeX
|
|
# parser) from %CommandHash, and merges it with the input token list
|
|
# If a command takes any required arguments, it will be a report_args,
|
|
# but if it just takes an optional argument, it can stay a regular old token.
|
|
# In either case, we insert a new field, "relyx_args", into the token list,
|
|
# which is the expected order of arguments for that command. Even if there
|
|
# are no args, we insert an empty relyx_args, so that we can differentiate
|
|
# between a truly unknown token and a known token which takes no args.
|
|
#
|
|
# We don't allow a new command to override a command that already exists in
|
|
# OldHash, i.e., one that was defined explicitly in the calling sub.
|
|
#
|
|
# Arg0 is a (reference to an) existing token list
|
|
my $OldHashRef = shift;
|
|
|
|
foreach (keys %CommandHash) {
|
|
my $val = $CommandHash{$_};
|
|
my ($entry, $count, @foo);
|
|
if (!exists $OldHashRef->{$_}) {
|
|
if ($count = scalar(@foo = ($val =~ /r/gi))) {
|
|
# TeX.pm will make this a TT::BegArgsToken and $count-1
|
|
# TT::ArgTokens, followed by a TT::EndArgsToken
|
|
$OldHashRef->{$_} = {"Type" => 'report_args',
|
|
"count" => $count,
|
|
"relyx_args" => $val};
|
|
} else { # only non-required args
|
|
# Make it a regular TT::Token, but write relyx_args
|
|
# (even if $val is "")
|
|
$OldHashRef->{$_} = {"relyx_args" => $val};
|
|
}
|
|
}
|
|
} # end foreach
|
|
|
|
} # end sub Merge
|
|
|
|
############################ READ LAYOUTS ####################################
|
|
sub read_layout_files {
|
|
# This subroutine reads a textclass-specific layout file and all files
|
|
# included in that file.
|
|
# It sets up the layout hash table. For each environment, it describes which
|
|
# layout that environment refers to. It does the same for macros which
|
|
# begin LyX layouts (e.g., \section)
|
|
# If we read a command that's not already in CommandHash, it means that this
|
|
# layout has some commands that aren't in syntax.default. If so, we ASSUME
|
|
# that the command takes just one required argument, and put it in
|
|
# CommandHash, so that &Merge will eventually put these commands into the
|
|
# token lists.
|
|
#
|
|
# TODO: we actually need to allow more sophisticated stuff. E.g. \foilhead
|
|
# is converted to Foilhead or ShortFoilHead (foils.layout) depending on whether
|
|
# the command has "r" or "or" arguments. Reading LatexParam (if it exists)
|
|
# can help us with this.
|
|
# Default is "r". Just unshift other args as you read them, since latexparam
|
|
# is put in between macro & the argument
|
|
# TODO: We need to store ToLayout s.t. we can have > 1 layout per command.
|
|
# Maybe by default just have one layout, but if (ref(layout)) then read
|
|
# more args & thereby figure out which layout?
|
|
#
|
|
# Arg0 is the name of the documentclass
|
|
use FileHandle;
|
|
use File::Basename;
|
|
my $doc_class = shift;
|
|
my @filestack;
|
|
my $fh;
|
|
my $line;
|
|
# ToLayout{latexname} stores layout; so ReversHash{layout} = latexname
|
|
my %ReverseHash;
|
|
my $debug_on = (defined($main::opt_d) && $main::opt_d);
|
|
|
|
# look for layout file in $HOME/.lyx first, then system layouts directory
|
|
my $searchname = "$doc_class.layout";
|
|
my @searchdirs = ();
|
|
my $personal_layout = "$main::dot_lyxdir/layouts";
|
|
push(@searchdirs,$personal_layout) if -e $personal_layout;
|
|
my $system_layout = "$main::lyxdir/layouts";
|
|
# I guess this won't exist if running reLyX without installing...
|
|
# Of course, in that case, this will probably break
|
|
push(@searchdirs,$system_layout) if -e $system_layout;
|
|
my @foundfiles = grep(-e "$_/$searchname", @searchdirs) or
|
|
die "Cannot find layout file $searchname in dir(s) @searchdirs";
|
|
my $LayoutFileName = "$foundfiles[0]/$searchname"; # take first one we found
|
|
|
|
$fh = new FileHandle;
|
|
$fh->open ("<$LayoutFileName");
|
|
my $zzz=$debug_on ? "$LayoutFileName" :"";
|
|
warn "Reading layout file $zzz\n";
|
|
push @filestack, $fh;
|
|
|
|
# Read the layout file!
|
|
my ($lyxname, $latexname, $latextype, $latexparam, $keepempty);
|
|
my $fname;
|
|
while() {
|
|
# Read a line. If eof, pop the filestack to return to the file
|
|
# that included this file *or* finish if the stack's empty
|
|
unless (defined ($line = <$fh>)) {
|
|
$fh->close;
|
|
pop @filestack;
|
|
last unless ($#filestack+1); # finish when stack is empty
|
|
$fh = $filestack[-1];
|
|
next; # read another line from the "calling" file
|
|
}
|
|
|
|
# Skip blank lines
|
|
next if $line =~ /^\s*$/;
|
|
|
|
# Split the line. Use limit 2 since there may be whitespace in 2nd term
|
|
my ($field_name, $field_stuff) = split(' ', $line, 2);
|
|
$field_name = lc($field_name); # LyX is case insensitive for fields
|
|
if (defined($field_stuff)) {
|
|
$field_stuff =~ s/^\"(.*)\"/$1/;
|
|
chomp ($field_stuff);
|
|
# Since split is limited to 2 fields, there may be extra whitespace
|
|
# at end. LyX breaks on a "\layout Abstract " command!
|
|
$field_stuff =~ s/\s*$//;
|
|
}
|
|
|
|
# This set of ifs deals with lines outside a style definition
|
|
if ($field_name eq "style") { # start a style definition
|
|
$lyxname = $field_stuff;
|
|
# Styles in LyX have spaces, but _ in layout files
|
|
$lyxname =~ s/_/ /g;
|
|
$latexname = ""; # make sure these variables are unset
|
|
$latextype = "";
|
|
$latexparam = "";
|
|
$keepempty = 0;
|
|
} elsif ($field_name eq "input") { #include a file
|
|
$searchname = $field_stuff;
|
|
@foundfiles = grep(-e "$_/$searchname", @searchdirs) or
|
|
die "Cannot find layout file $searchname in dir(s) @searchdirs";
|
|
$fname = "$foundfiles[0]/$searchname"; # take first one we found
|
|
$fh = new FileHandle;
|
|
push @filestack, $fh;
|
|
$fh->open("<$fname");
|
|
print "Reading included layout file $fname\n" if $debug_on;
|
|
}
|
|
|
|
next unless $lyxname; # not w/in style definition
|
|
|
|
# This set of ifs deals with lines within a Style definition
|
|
if ($field_name eq "latexname") {
|
|
$latexname = $field_stuff;
|
|
next;
|
|
} elsif ($field_name eq "latexparam") {
|
|
#$dum = $field_stuff;
|
|
$latexparam = $field_stuff;
|
|
next;
|
|
} elsif ($field_name eq "latextype") {
|
|
$latextype = $field_stuff;
|
|
next;
|
|
} elsif ($field_name eq "keepempty") {
|
|
$keepempty = $field_stuff;
|
|
next;
|
|
} elsif ($field_name eq "copystyle") { # copy an existing style
|
|
# "if" is necessary in case someone tries "CopyStyle Standard"
|
|
if (exists $ReverseHash{$field_stuff}) {
|
|
my $layref = $ReverseHash{$field_stuff};
|
|
$latexname = $layref->{"name"};
|
|
$latextype = $layref->{"type"};
|
|
$latexparam = $layref->{"param"};
|
|
$keepempty = $layref->{"keepempty"};
|
|
}
|
|
|
|
# When you get to the end of a definition, create hash table entries
|
|
# (if you've found the right information)
|
|
} elsif ($field_name eq "end") {
|
|
|
|
if ($latextype and $latexname) {
|
|
# Create reverse hash entry (needed for CopyStyle)
|
|
# Do it before making modifications to $latexname, e.g.
|
|
$ReverseHash{$lyxname} = {"name" => $latexname,
|
|
"type" => $latextype,
|
|
"param" => $latexparam,
|
|
"keepempty" => $keepempty,
|
|
};
|
|
|
|
my ($nest, $skip) = (0,0);
|
|
for ($latextype) { # make $_=$latextype
|
|
if (/^Command/) {
|
|
# Macros need a '\' before them. Environments don't
|
|
$latexname = '\\' . $latexname;
|
|
|
|
# Create the command if it wasn't in syntax.default
|
|
unless (exists $CommandHash{$latexname}) {
|
|
$CommandHash{$latexname} = "r";
|
|
}
|
|
|
|
} elsif (/^Environment/) {
|
|
$nest = 1;
|
|
} elsif (/Item_Environment/i || /List_Environment/) {
|
|
$nest = 1;
|
|
|
|
# layout Standard has LatexType Paragraph. It shouldn't
|
|
# have any hash entry at all
|
|
} elsif (/^Paragraph$/) {
|
|
$skip = 1;
|
|
} else {
|
|
warn "unknown LatexType $latextype" .
|
|
"for $latexname (layout $lyxname)!\n";
|
|
}
|
|
# Handle latexparam, if any
|
|
# if ($dum =~ s/^"(.*)"/$1/) { # newer layout file syntax
|
|
# while ($dum =~ /^[[{]/) {
|
|
# $dum =~ s/\[.*?\]// && ($latexargs = "o$latexargs") or
|
|
# $dum =~ s/\{.*?\}// && ($latexargs = "r$latexargs");
|
|
# }
|
|
# warn "leftover LatexParam stuff $dum" if $dum;
|
|
# } else { # 0.12.0
|
|
# if ($latextype eq "Command") {optarg}
|
|
# else {req. arg}
|
|
# }
|
|
} #end for
|
|
|
|
# Create the hash entry
|
|
unless ($skip) {
|
|
$ToLayout->{$latexname} = {"layout" => $lyxname,
|
|
"nestable" => $nest,
|
|
"keepempty" => $keepempty};
|
|
}
|
|
|
|
# Now that we've finished the style, unset $lyxname so that
|
|
# we'll skip lines until the next style definition
|
|
$lyxname = "";
|
|
} # end if ($latextype and $latexname)
|
|
} # end if on $line
|
|
|
|
} #end while
|
|
|
|
## Print every known layout
|
|
# print " LatexName Layout Keepempty?\n";
|
|
# foreach (sort keys %$ToLayout) {
|
|
# printf "%20s%15s %1d\n",$_,$ToLayout->{$_}{'layout'},
|
|
# $ToLayout->{$_}{'keepempty'};
|
|
# };
|
|
#warn "Done reading layout files\n";
|
|
return;
|
|
} # end sub read_layout_files
|
|
|
|
|
|
|
|
1; # return TRUE to calling routine
|