#!/usr/bin/env perl # File : makeglossaries # Author : Nicola Talbot # Version : 4.54 # Description: simple Perl script that calls makeindex or xindy. # Intended for use with "glossaries.sty" (saves having to remember # all the various switches) # This file is distributed as part of the glossaries LaTeX package. # Copyright 2007-2023 Nicola L.C. Talbot # This work may be distributed and/or modified under the # conditions of the LaTeX Project Public License, either version 1.3 # of this license or any later version. # The latest version of this license is in # http://www.latex-project.org/lppl.txt # and version 1.3 or later is part of all distributions of LaTeX # version 2005/12/01 or later. # # This work has the LPPL maintenance status `maintained'. # # The Current Maintainer of this work is Nicola Talbot. # This work consists of the files glossaries.dtx and glossaries.ins # and the derived files glossaries.sty, glossaries-prefix.sty, # glossary-hypernav.sty, glossary-inline.sty, glossary-list.sty, # glossary-long.sty, glossary-longbooktabs.sty, glossary-longragged.sty, # glossary-mcols.sty, glossary-super.sty, glossary-superragged.sty, # glossary-tree.sty, glossaries-compatible-207.sty, # glossaries-compatible-307.sty, glossaries-accsupp.sty, # glossaries-babel.sty, glossaries-polyglossia.sty, glossaries.l2h. # Also makeglossaries and makeglossaries-lite.lua. my $version="4.54 (2024-04-03)"; # History: # v4.51 - v4.54: # * No change. (Version number updated in line with glossaries.sty) # v4.50: # * Added -e (don't fix encap clashes) # * Add check for glsignore with encap clashes # * Don't fix encap clash if counter or prefix different # * Added check for illegal page # * Added brazil, brazilian, canadian and canadien mappings # v4.48-4.49: # * No change. (Version number updated in line with glossaries.sty) # v4.47: # * Added hybrid instructions if record option detected but not # \makeglossaries # * Added quote_if_spaced subroutine # v4.35 - v4.46: # * No change. (Version number updated in line with glossaries.sty) # v4.34: # * Added check for \glsxtr@resource # v4.33: # * Version number synchronized with glossaries.sty # v2.21: # * Fixed spelling of \GlsAddXdyLocation # * Adjusted range encap clash # v2.20 (2016/12/16) # * Added check for \glsxtr@makeglossaries # v2.19 (2016/05/27) # * Improved check in &parse_for_xindy_nosort # v2.18 (2016/01/24) # * Added &parse_for_xindy_nosort to help diagnose xindy's empty # index key warning/error. # * Added check for makeindex's multiple encap warning. # v2.17 (2015/11/30) # * Escaped { in regular expressions. # http://www.dickimaw-books.com/cgi-bin/bugtracker.cgi?action=view&key=101 # v2.16 (2015/06/28) # * Added check for "german" and codepage that doesn't contain # "din5007", "duden" or "braille". If missing, "din5007-" is # prefixed to the codepage. # v2.15 (2014/07/30) # * Removed hard-coded three character extension assumption # www.dickimaw-books.com/cgi-bin/bugtracker.cgi?action=view&key=55 # * Added message to indicate the number of ignored glossaries # when the extension is specified. # v2.14 (2014/03/06) # * Added -Q and -k options # v2.13 (2014/01/21) # * Added check for leading and trailing quotes in filename # v2.12 (2014/01/20) # * Added check for '*' in istfilename # * Fixed bug report # http://www.dickimaw-books.com/cgi-bin/bugtracker.cgi?action=view&key=33 # * Added error text hashes # v2.11 (2014/01/17): # * Added check for backslashes in -x, -m and -d paths # * Added double-quotes around $appname in &run_app # v2.10 (2013/12/01): # * initialise $language to suppress warnings # v2.09 (2013-11-12): # * added check for -q switch when issuing warnings. # v2.08 (2013-10-14): # * added -x and -m options # v2.07 (2013-06-17): # * added check for success on chdir if -d used # * added dialect map # v2.06 (2013-04-21): # * added "din5007" as default if language is set to "german" # v2.05 (2012-11-12): # * added -d option # v2.04 (2012-04-19): # * fixed bug in &scan_aux # v2.03 (2011-04-12): # * added warning about possibly needing 'nomain' package option # v2.02 (2011-04-2): # * Prints version number at start of run unless -q # * Added more diagnostics. # v2.01 (2010-09-29): # * Added 'use warnings' # v2.0 (2010-06-30) : # * Made file handle local in &scan_aux # v1.9 (2010-06-14) : # * Check for \@input # v1.8 (2009-11-03) : # * Create an empty output file if the input file is empty # without calling xindy/makeindex # v1.7 (2009-09-23) : # * Issue warning rather than error when empty/non existant file # checks fail # v1.6 (2009-05-24) : # * main glossary no longer automatically added # (only added if information in aux file) # * if file extension is specified, check added to ensure it # corresponds to a known glossary extension. # * added file existance test and file empty test # v1.5 (2008-12-26) : # * added support for xindy # * picks up ordering information from aux file # v1.4 (2008-05-10) : # * added support for filenames with spaces. # v1.3 (2008-03-08) : # * changed first line from /usr/bin/perl -w to /usr/bin/env perl # (Thanks to Karl Berry for suggesting this.) # v1.2 (2008-03-02) : # * added support for --help and --version # * improved error handling # v1.1 (2008-02-13) : # * added -w and strict # * added check to ensure .tex file not passed to makeglossaries # # v1.0 (2007-05-10) : Initial release. use Getopt::Std; use strict; # v2.01 added the following line use warnings; # v2.05 added $opt_d # v2.08 added $opt_x and $opt_m # v4.50 added $opt_e use vars qw($opt_q $opt_t $opt_o $opt_s $opt_p $opt_g $opt_c $opt_r $opt_l $opt_i $opt_L $opt_n $opt_C $opt_d $opt_x $opt_m $opt_Q $opt_k $opt_e); $Getopt::Std::STANDARD_HELP_VERSION = 1; # v1.5 added -L for xindy (but language can be specified in # .tex file) # v1.5 added -n (print the command that would be issued but # don't actually run the command) getopts('s:o:t:p:L:C:ilqQkrcgnd:x:m:e'); unless ($#ARGV == 0) { die "makeglossaries: Need exactly one file argument.\n", "Use `makeglossaries --help' for help.\n"; } # v2.02: added: print "makeglossaries version $version\n" unless ($opt_q); # v2.07: added babel dialect -> xindy language map: my %languagemap = ( 'american' => 'english', 'austrian' => 'german', 'brazil' => 'portuguese',# v4.50 'brazilian' => 'portuguese',# v4.50 'british' => 'english', 'canadian' => 'english',# v4.50 'canadien' => 'french',# v4.50 'francais' => 'french', 'frenchb' => 'french', 'germanb' => 'german', 'lsorbian' => 'lower-sorbian', 'magyar' => 'hungarian', 'naustrian' => 'german', 'ngermanb' => 'german', 'ngerman' => 'german', # v2.16 'norsk' => 'norwegian', 'portuges' => 'portuguese', 'russianb' => 'russian', 'slovene' => 'slovenian', 'UKenglish' => 'english', 'USenglish' => 'english', 'ukraineb' => 'ukrainian', 'usorbian' => 'upper-sorbian' ); # v2.07 added check for success and for -q # v2.05 added: if ($opt_d) { # v2.11 check for backslash character if on Windows $opt_d=~s/\\/\//g if $^O=~/Win/; if (chdir $opt_d) { print "Changed to '$opt_d'\n" unless ($opt_q); } else { die "Unable to chdir to '$opt_d' $!\n"; } } # v2.02: added: # v2.19: changed value my $xdynotist = 2; # v2.13: removed #my $needcompatibilitymode=2; # v2.13: added $istnotfound my $istnotfound = 3; # v2.13: added error text: my %makeindex_error_text = ( $xdynotist => "Style name indicates makeindex, but may be in xindy format.\n" . "Remember to use \\setStyleFile to specify the name\n" . "of the style file rather than redefining \\istfilename\n" . "explicitly.", $istnotfound => "Style file not found. (Have you used \\noist by mistake?)" ); my %xindy_error_text = ( 'nosort' => "Sort key required for entries only containing command names.", 'istnotxdy' => "Style name has xdy extension, but may be in makeindex format.\n" . "Remember to use \\setStyleFile to specify the name\n" . "of the style file rather than redefining \\istfilename\n" . "explicitly.", 'missingendquote' => "You may have missed a \" character in a command such as \\GlsAddXdyLocation.", 'nolanguage' => "No language detected.\nHave you remembered to use \\printglossary\n". "or \\printglossaries in your document?", 'nomain' => "\nRemember to use package option 'nomain' if you don't\n". "want to use the main glossary.", #v2.21: fixed spelling of \GlsAddXdyLocation #v4.50: added reference to esclocations 'badlocation' => "You may have forgotten to add a location \n" . "class with \\GlsAddXdyLocation or you may have \n" . "the format incorrect or you may need \nthe package option esclocations=true.\n", 'comp207' => "You may need to add 'compatible-2.07' package option.", 'noxdyfile' => "Style file not found. (Have you used \\noist by mistake?)" ); # define known extensions # v1.6: removed adding main glossary here as there's no guarantee # that it's been used. If it has been used, the information will # be picked up later in the aux file my %exttype = ( # main => {in=>'glo', out=>'gls', 'log'=>'glg'}, ); # v1.5 define require languages for xindy my %language = (); my %codepage = (); my $ext = ''; my $name = $ARGV[0]; # v2.13 added: #$name=~s/^"(.*)"$/$1/; # v2.15: fix provided by Herb Schulz to allow for extensions that # aren't exactly three characters: # (HS) changes fix for 3 character extension limit my ($basename,$extension) = ($name =~ m/^(.*?)(?:\.([^\.]*))?$/); # (HS) end of changes fix for 3 character extension limit # make sure $extension is defined. (If there was no match in the # above, it cause an uninitialised error later.) $extension = '' unless ($extension); # Make sure users don't try passing the tex file: #if (length($ARGV[0]) > 3 and substr($ARGV[0],-4,1) eq ".") #{ # $name = substr($ARGV[0],0,length($ARGV[0])-4); # # $ext = substr($ARGV[0],-3,3); # # if (lc($ext) eq 'tex') # (HS) changes fix for 3 character extension limit #if (length($ARGV[0]) > 3 and substr($ARGV[0],-4,1) eq ".") #{ # $name = substr($ARGV[0],0,length($ARGV[0])-4); # # $ext = substr($ARGV[0],-3,3); # # if (lc($ext) eq 'tex') if (lc($extension) eq 'tex') { die("Don't pass the tex file to makeglossaries:\n" ."either omit the extension to make all the glossaries, " ."or specify one of the glossary files, e.g. $name.glo, to " ."make just that glossary.\n") } #} # (HS) end of fix for 3 character extension limit # (HS) changes fix for 3 character extension limit $ext = $extension; $name = $basename; # (HS) end of changes fix for 3 character extension limit # v2.01 add check to see if aux file exists unless (-e "$name.aux") { die "Auxiliary file '$name.aux' doesn't exist. Have you run LaTeX?\n"; } my $istfile = ""; my $glslist = ""; # should letter ordering be used? (v1.5 added) my $letterordering = defined($opt_l); # v2.19 added: my $extramkidxopts = ''; # v4.34 added: my $foundbib2glsresource = ''; # Check aux file for other glossary types, # style file name and various other bits of information. &scan_aux($name); # has the style file been specified? unless ($istfile) { if ($foundbib2glsresource) { # v4.47 added extra hybrid instructions die "Found \\glsxtr\@resource in '$name.aux',\n", "but not found \\\@istfilename.\n", "You need to run bib2gls not makeglossaries.\n", "If you have used record=alsoindex or record=hybrid\n", "then add \\makeglossaries to your preamble.\n"; } else { die "No \\\@istfilename found in '$name.aux'.\n", "Did your LaTeX run fail?\n", "Did your LaTeX run produce any output?\n", "Did you remember to use \\makeglossaries?\n"; } } # v2.08 added $xindyapp and $makeindexapp # By default assume they are on the operating system's path my $xindyapp = ($opt_x ? $opt_x :'xindy'); my $makeindexapp = ($opt_m ? $opt_m :'makeindex'); my $fixencapclash = ($opt_e ? 0 : 1); # v2.11 check for backslash character if on Windows if ($^O=~/Win/) { $xindyapp=~s/\\/\//g; $makeindexapp=~s/\\/\//g; } # v1.5 save the general xindy switches my $xdyopts = ''; # v2.01 replaced 'unless ($opt_L eq "")' with 'if ($opt_L)' if ($opt_L) { $xdyopts .= " -L $opt_L"; } # save all the general makeindex switches my $mkidxopts = ''; if ($opt_i) { # This option is of limited use as the basename must be supplied to # makeglossaries $mkidxopts .= " -i"; } if ($letterordering) { $mkidxopts .= " -l"; $xdyopts .= " -M ord/letorder"; } if ($opt_q) { # v2.01 removed following lines. (This script now deals with # printing messages to STDOUT.) #$mkidxopts .= " -q"; #$xdyopts .= " -q"; } if ($opt_r) { $mkidxopts .= " -r"; } if ($opt_c) { $mkidxopts .= " -c"; } if ($opt_g) { $mkidxopts .= " -g"; } # v2.01 replaced 'unless ($opt_p eq "")' with 'if ($opt_p)' if ($opt_p) { $mkidxopts .= " -p $opt_p"; } if ($extramkidxopts) { $mkidxopts .= " $extramkidxopts"; } # v2.01 replaced 'unless ($opt_s eq "")' with 'if ($opt_s)' if ($opt_s) { # v2.01 check if user has specified -s .ist but aux file # indicates .xdy ought to be used and vice-versa. Also check if # requested style file exists unless (-e $opt_s) { die "\n", "Requested style file '$opt_s' doesn't exist.\n\n"; } if ($istfile=~/\.xdy$/ and $opt_s!~/\.xdy$/) { die "\n", "The auxiliary file indicates that you should be using xindy,\n", "but you have specified makeindex style file '$opt_s'\n", "Make sure you don't specify 'xindy' as a package option if\n", "you want to use makeindex.\n\n", "\\usepackage[makeindex]{glossaries}\n\n"; } elsif ($istfile!~/\.xdy$/ and $opt_s=~/\.xdy$/) { die "\n", "The auxiliary file indicates that you should be using\n", "makeindex, but you have specified xindy style file '$opt_s'.\n", "Make sure you specify 'xindy' as a package option if you\n", "want to use xindy.\n\n", "\\usepackage[xindy]{glossaries}\n\n"; } $istfile = $opt_s; } # Use xindy if style file ends with .xdy otherwise use makeindex my $usexindy = ($istfile=~m/\.xdy\Z/); if ($ext) { # an extension has been specified, so only process # the specified file # v1.6 %thistype is no longer given a default value my %thistype; my $thislang = ""; my $thiscodepage = ""; my @types = ($glslist ? split /,/, $glslist : keys %exttype); foreach my $type (@types) { if ($exttype{$type}{'in'} eq $ext) { %thistype = %{$exttype{$type}}; $thislang = $language{$type}; $thiscodepage = $codepage{$type}; last; } } # v1.6 If %thistype hasn't been defined, then the given # extension doesn't correspond to any known glossary type # v2.01 replaced deprecated 'defined(%thistype)' with %thistype unless (%thistype) { die "The file extension '$ext' doesn't correspond to any\n", "known glossary extension. Try running makeglossaries\n", "without an extension, e.g. makeglossaries \"$name\".\n"; } my $outfile; # v2.01 replaced 'if ($opt_o eq "")' with 'unless ($opt_o)' unless ($opt_o) { $outfile = "$name.$thistype{out}"; } else { $outfile = $opt_o; } my $transcript; # v2.01 replaced 'if ($opt_t eq "")' with 'unless ($opt_t)' unless ($opt_t) { $transcript = "$name.$thistype{'log'}"; } else { $transcript = $opt_t; } # v2.01 remove old transcript file unless ($opt_n) { unlink($transcript); } if ($usexindy) { &xindy("$name.$ext", $outfile, $transcript,$istfile, $thislang, $thiscodepage, $xdyopts, $opt_q, $opt_n, $xindyapp); } else { &makeindex("$name.$ext",$outfile,$transcript,$istfile, $mkidxopts,$opt_q, $opt_n, $makeindexapp, $fixencapclash); } # v2.15 added: my $num_omitted = $#types; if ($num_omitted == 1) { print "1 glossary ignored.\n" unless $opt_q; } elsif ($num_omitted > 1) { print "$num_omitted glossaries ignored.\n" unless $opt_q; } } else { # no file extension specified so process all glossary types my @types = ($glslist ? split /,/, $glslist : keys %exttype); foreach my $type (@types) { my %thistype = %{$exttype{$type}}; my $inputfile = "$name.$thistype{in}"; my $outfile; # v2.01 changed 'if ($opt_o eq "")' with 'unless ($opt_o)' unless ($opt_o) { $outfile = "$name.$thistype{out}"; } else { $outfile = $opt_o; } # v1.7 print warnings to STDOUT instead of STDERR # v1.6 added file existence test unless (-e $inputfile) { # v2.09 suppress warning if -q switch in use print "Warning: File '$inputfile' doesn't exist.\n", "*** Skipping glossary '$type'. ***\n" unless ($opt_q); next; } unless (-r $inputfile) { print "Warning: No read access for '$inputfile' $!\n", "*** Skipping glossary '$type'. ***\n" unless ($opt_q); next; } my $transcript; # v2.01 changed 'if ($opt_t eq "")' with 'unless ($opt)' unless ($opt_t) { $transcript = "$name.$thistype{'log'}"; } else { $transcript = $opt_t; } # v1.6 added file empty test if (-z $inputfile) { my $message = "Warning: File '$inputfile' is empty.\n". "Have you used any entries defined in glossary '$type'?\n"; if ($type eq 'main') { $message .= "Remember to use package option 'nomain' if you\n". "don't want to use the main glossary.\n"; } # v2.09 suppress warning if -q switch in use warn $message unless $opt_q; # Write warning to transcript file. if (open TRANSFD, ">$transcript") { print TRANSFD $message; close TRANSFD; } # create an empty output file and move on to the next glossary if (open OFD, ">$outfile") { print OFD "\\null\n"; close OFD; } else { warn "Unable to create '$outfile' $!\n"; } next; } # v2.01 remove old transcript file unless ($opt_n) { unlink($transcript); } if ($usexindy) { &xindy($inputfile,$outfile,$transcript,$istfile, $language{$type},$codepage{$type}, $xdyopts,$opt_q,$opt_n, $xindyapp); } else { &makeindex($inputfile,$outfile,$transcript, $istfile,$mkidxopts,$opt_q,$opt_n, $makeindexapp, $fixencapclash); } } } sub scan_aux{ my $name = shift; # v2.0 added local(*AUXFILE); if (open AUXFILE, "$name.aux") { while () { #v2.14 added if (m/\\\@gls\@reference/ and not $glslist) { die "Your document has used \\makenoidxglossaries\n", "You don't need makeindex or xindy.\n"; } if (m/\\glsxtr\@makeglossaries\{(.*)\}/) { #v2.20 added $glslist = $1; unless ($opt_q) { print "only processing subset '$glslist'\n"; } } elsif (m/\\\@input\{(.+)\.aux\}/) { # v1.9 added # v2.17 escaped { in regex &scan_aux($1); # v2.04 added # (Fix provided by Daniel Grund) next; } elsif (m/\\\@newglossary\s*\{(.*)\}\{(.*)\}\{(.*)\}\{(.*)\}/) { # v2.17 escaped { in regex $exttype{$1}{'log'} = $2; $exttype{$1}{'out'} = $3; $exttype{$1}{'in'} = $4; unless ($opt_q) { print "added glossary type '$1' ($2,$3,$4)\n"; } } elsif (m/\\\@istfilename\s*\{([^}]*)\}/) { # v2.17 escaped { in regex $istfile = $1; # check if double quotes were added to \jobname # v2.12 added check for xdy (bug ID 33) $istfile=~s/^"(.*)"\.(ist|xdy)$/$1.$2/; # v2.12 if on Windows, substitute any '*' with ' ' # to compensate for MiKTeX bug # http://sourceforge.net/p/miktex/bugs/2301/ if ($^O=~/Win/) { $istfile=~tr/\*/ /; } } elsif (m/\\\@xdylanguage\s*\{([^}]+)\}\{([^}]*)\}/) { # v1.5 added # v2.17 escaped { in regex $language{$1} = $2; } elsif (m/\\\@gls\@codepage\s*\{([^}]+)\}\{([^}]*)\}/) { # v1.5 added # v2.17 escaped { in regex $codepage{$1} = $2; } elsif (m/\\\@gls\@extramakeindexopts\{(.*)\}/) { # v2.19 added $extramkidxopts .= $1; } elsif (m/\\glsxtr\@resource/) { # v4.34 added # No error at this point as a hybrid method is allowed. # Error only occurs if no \@istfilename found. $foundbib2glsresource = 1; } # v1.5 added # Allow -l switch to override specification in aux file unless (defined($opt_l)) { # v2.17 escaped { in regex if (m/\\\@glsorder\s*\{([^}]+)\}/) { my $ordering = $1; if ($ordering eq "word") { $letterordering = 0; } elsif ($ordering eq "letter") { $letterordering = 1; } else { warn "Unknown ordering '$ordering'\n", "Assuming word ordering\n" unless ($opt_q); $letterordering = 0; } } } } close AUXFILE; } else { warn "Unable to open $name.aux: $!\n"; } } # v4.47 new # Issue #129 was specifically about the Lua script but it's # likely the same problem would occur with this Perl script given # the same version of cmd.exe sub quote_if_spaced{ my ($str) = @_; if ($str=~/ /) { $str = "\"$str\""; } $str; } # v2.01 new subroutine run_app added sub run_app{ my($appname, $appargs, $trans, $quiet, $dontexec) = @_; local(*STATUS); my $status = ''; my $warnings = ''; my $errno = 0; my $log = ''; print "$appname $appargs\n" if ($dontexec or not $quiet); return if ($dontexec); # v4.47 my $cmdstr = "e_if_spaced($appname) . " $appargs"; # v2.11 added double-quotes around $appname # v2.14 added -k switch if (not $opt_k and open (STATUS, "$cmdstr 2>&1 |")) { while () { print unless ($quiet); $warnings .= $_ if (/WARNING:/); $status .= $_ unless (/^[\w]+ing/ or /^Finished/ or /^Usage:/); $log .= $_; } close STATUS; } else { unless ($opt_k) { $warnings = "Unable to fork \"$appname\" with redirection $!\n"; # v2.14 added check for quiet switches warn $warnings, "Retrying without redirection.\n" unless ($quiet or $opt_Q); } $status = `$cmdstr`; $log = $status; print $status unless ($quiet); } if ($?) { # v2.14 Fixed error number assignment $errno = ($? >> 8); # v2.14 Added check for existence of transcript unless (-e $trans) { $log .= "\n$appname didn't create a transcript.\n"; if (not $appname=~/\//) { # No path specified $log .= "Check that $appname has been installed and\nis on your system's PATH.\n"; if (defined $ENV{PATH}) { $log .= "Your PATH environment variable is set to: \n" . $ENV{PATH} . "\n"; } else { $log .= "I'm sorry, I can't detect your PATH environment variable\n"; } $log .= "Try manually running:\n\"$appname\" $appargs\n"; } elsif (not -e $appname) { if ($^O=~/Win/) { if (-e "$appname.exe" or -e "$appname.bat") { $log .= "Try manually running:\n\"$appname\" $appargs\n"; } else { $log .= "File '$appname' doesn't exist.\n"; } } else { $log .= "File '$appname' doesn't exist.\n"; } } else { $log .= "Try manually running:\n\"$appname\" $appargs\n"; } } if (open LOGFILE, ">>$trans") { print LOGFILE "\n\n*** Unable to execute: '\"$appname\" $appargs' ***\n\n"; print LOGFILE "Failed with error code $errno.\n\n"; if ($log) { print LOGFILE "Status report:\n\n"; print LOGFILE "$log", "\n"; } print LOGFILE "Warnings:\n\n",$warnings, "\n" if $warnings; close LOGFILE; } else { warn "Unable to open '$trans' $!\n"; } } my $accepted = 1; my $rejected = 0; if ($log=~/\((\d+) entries accepted, (\d+) rejected\)/) { $accepted = $1; $rejected = $2; } if ($accepted == 0 or $rejected > 0 or ( ($log=~/(\d+) warnings?/ and $1 gt 0)) or $log=~/not found/) { # Attempt to diagnose what's gone wrong if (open TRANS, $trans) { while () { if (/Index style file .* not found/) { $errno = $istnotfound; last; } elsif (/Unknown specifier ;/) { $errno = $xdynotist; last; } elsif (/## Warning \(.*\):\s*$/ or /^!! Input index error/) { # v4.50: added check for Input index error $warnings .= $_ . ; } } close TRANS; } } return ($status, $warnings, $errno); } # v2.08 added $app parameter sub makeindex{ my($in,$out,$trans,$ist,$rest,$quiet,$dontexec,$app,$repairencaps) = @_; my $args = "$rest -s \"$ist\" -t \"$trans\" -o \"$out\" \"$in\""; # v2.01 replaced code with call to &run_app my ($status, $warnings, $errno) = &run_app($app, $args, $trans, $quiet, $dontexec); return if ($dontexec); if ($errno) { my $diagnostic = ''; if ($makeindex_error_text{$errno}) { $diagnostic = $makeindex_error_text{$errno}; } else { $diagnostic = "Can't find diagnostic message for error code $errno"; } if (open LOGFILE, ">>$trans") { print LOGFILE "\nmakeglossaries diagnostic messages:\n$diagnostic\n"; close LOGFILE; } die "\n***Call to makeindex failed***\n", ($diagnostic ? "\nPossible cause of problem:\n\n". $diagnostic . "\n\n": "\n" ), "Exit status: $errno. Check '$trans' for details\n"; } # 2.18: added check and repair for multiple encaps if ($warnings) { warn $warnings unless $quiet; my $multiencaps = $repairencaps and ($warnings=~/multiple encaps/); # 4.50: added check for illegal page and cs in page my $illegalpage = ($warnings=~/Illegal [A-Za-z]+ (number|digit)/); # 'Illegal space within numerals in second argument' # and 'No closing delimiter for second argument' most likely # mean that a command has ended up in the location my $csinlocation = ($warnings=~/(Illegal space|No closing delimiter).* second argument/); # 4.50: added check for illegal page number if ($multiencaps or $illegalpage or $csinlocation) { unless ($quiet) { my $msg = ''; if ($multiencaps) { $msg = "multiple encaps"; } if ($illegalpage) { if ($msg) { $msg .= ', '; } $msg .= "illegal page number"; } if ($csinlocation) { if ($msg) { $msg .= ', '; } $msg .= "potential LaTeX commands in location"; } print "Encap/location issue: $msg detected. Attempting to remedy.\n"; print "Reading $in...\n"; } my $page_invalid_regex = '[^a-zA-Z0-9\.]'; my $pagecomp = '.'; my $regexpagecomp = '\.'; if ($illegalpage) { # get the page compositor from the .ist file if (open INFD, $ist) { while () { if (/page_compositor "(.)"/) { $pagecomp = $1; last; } } close INFD; } else { warn "Can't open '$ist' $!\n"; } if ($pagecomp eq '.' or $pagecomp eq '-' or $pagecomp eq '\\'or $pagecomp eq ']') { $regexpagecomp = "\\$pagecomp"; } else { $regexpagecomp = $pagecomp; } if ($pagecomp ne '.') { $page_invalid_regex = "[^a-zA-Z0-9$regexpagecomp]"; } } # if $in can't be opened at this point something wrong # has occurred (otherwise how did makeindex manage to open # it?) open INFD, $in or die "Can't open '$in' $!\n"; my @entries = (); while () { # The encap usually starts with setentrycounter but may have # ( or ) to indicate the start or end of a range. # The encap will be glsseeformat for cross-references. # \glossentry and \subglossentry may be preceded by # \glsnonextpages or \glsnextpages if (/\\glossaryentry\{(.*(?:\\glossentry|\\subglossentry\{\d+\}))\{([^\|]*)\}\|([^\|]*)\}\{(.*)\}$/) { my %entry = ( 'entry' => "$1\{$2\}", 'label' => $2, 'encap' => $3, 'location' => $4, 'counter'=>'', 'prefix'=>'' ); # Find the page counter (not available with # cross-references) if ($entry{'encap'}=~/setentrycounter\[(.*)\]\{([a-zA-Z0-9]+)\}"\\/) { $entry{'prefix'} = $1; $entry{'counter'} = $2; } # First move any commands out of the location. if ($csinlocation) { my $loc = $entry{'location'}; my $encap = $entry{'encap'}; # This is only designed for locations in the form # \cs{num} but allow for \protect\cs if ($loc=~m/^(?:\\protect *)?\\([A-Za-z\@]+) *\{(.*)\}$/) { my $cs = $1; $loc = $2; # Normal glossary indexing will have the encap # start with setentrycounter[prefix]{counter} # This won't match a cross-reference (see) but # that has its location always set to Z. if ($encap=~/^(setentrycounter\[.*\]\{.*\}"\\)(.*)$/) { $entry{'encap'} = "$1glslocationcstoencap\{$2\}\{$cs\}"; unless ($quiet) { print "Invalid location '$entry{location}' detected for entry '$entry{label}'. Replaced with '$loc'\n"; } } elsif (not $quiet) { warn "Invalid location '$entry{location}' detected for entry '$entry{label}' has unexpected encap '$encap'. Location replaced with '$loc'\n"; } $entry{'location'} = $loc; } } if ($illegalpage) { my $loc = $entry{'location'}; if ($loc=~s/($page_invalid_regex)+/$pagecomp/g) { unless ($quiet) { print "Invalid location '$entry{location}' detected for entry '$entry{label}'. Replaced with '$loc'\n"; } } if ($loc=~s/^($regexpagecomp)+//) { unless ($quiet) { print "Invalid location '$entry{location}' (starts with page compositor) detected for entry '$entry{label}'. Replaced with '$loc'\n"; } } if ($loc=~s/($regexpagecomp)+$//) { unless ($quiet) { print "Invalid location '$entry{location}' (ends with page compositor) detected for entry '$entry{label}'. Replaced with '$loc'\n"; } } if ($loc eq '') { unless ($quiet) { print "Invalid empty location detected for entry '$entry{label}'. Replaced with 0 (encap: glsignore).\n"; } $loc = '0'; $entry{'encap'} = 'glsignore'; } $entry{'location'} = $loc; } my $add = 1; if ($multiencaps) { for (my $idx = 0; $idx <= $#entries; $idx++) { my $existing = $entries[$idx]; #v4.50: added check for prefix and counter # Since the encap includes the prefix and # counter information, the same location and # format for different counters or prefixes # will be considered an encap clash by # makeindex, but shouldn't be merged. if ($entry{'entry'} eq $existing->{'entry'} and $entry{'location'} eq $existing->{'location'} and $entry{'prefix'} eq $existing->{'prefix'} and $entry{'counter'} eq $existing->{'counter'} and $entry{'encap'} ne $existing->{'encap'}) { unless ($quiet) { if ($entry{'counter'} eq '' and $entry{'location'} eq 'Z') { print "Encap clash for entry '$entry{label}' possibly caused by multiple cross-reference instances\n"; } else { print "Encap clash detected for entry '$entry{label}', counter: '$entry{counter}', location: '$entry{location}'. Encap '$entry{encap}' clashes with '$existing->{'encap'}'\n"; } } $add = 0 if ($add == 1); # Range encaps should take precedence. if ($entry{'encap'}=~/^[\(\)]/) { $add = 2; if ($existing->{'encap'}=~/^[\(\)]/) { # too complicated, keep both last; } else { # remove existing splice @entries, $idx, 1; $idx--; } } elsif ($existing->{'encap'}=~/^[\(\)]/) { if ($entry{'encap'}=~/^[\(\)]/) { # too complicated, keep both $add = 1; last; } # (otherwise drop new entry) } elsif (&encap_overrides($entry{'encap'}, $existing->{'encap'})) { $entries[$idx] = \%entry; } } } } push @entries, \%entry if $add > 0; } else { warn "Abandoning attempt. Can't parse: $_" unless $quiet; close INFD; return; } } close INFD; if (open OUTFD, ">$in") { print "Writing $in...\n" unless $quiet; foreach my $entry (@entries) { print OUTFD '\\glossaryentry{', $entry->{'entry'}, '|', $entry->{'encap'}, '}{', $entry->{'location'}, '}', "\n"; } close OUTFD; print "Retrying\n" unless $quiet; &makeindex($in,$out,$trans,$ist,$rest,$quiet,$dontexec,$app,0); } else { warn "Can't open '$in' $!\n" unless $quiet; } } } } # 2.18 new # This is a subroutine in case of possible extension. # For example, what happens in the event of 'textbf' and 'emph'? # Should one override the other or be combined? Combining is harder # as it would need a corresponding LaTeX command. (bib2gls deals # with this better through its --map-formats switcn.) # 2.21 check for range encap moved earlier # 4.50 add check for glsignore sub encap_overrides{ my ($newencap, $existing) = @_; if ($newencap=~/\\glsignore\s*$/) { return 0; } elsif ($existing=~/\\glsignore\s*$/) { return 1; } ($existing=~/\\glsnumberformat\s*$/ or $newencap!~/\\glsnumberformat\s*$/ ) } # v2.08 added $app parameter sub xindy{ my($in,$out,$trans,$ist,$language,$codepage,$rest,$quiet, $dontexec, $app) = @_; my($args, $langparam, $main, $retry); my($module); $module = $ist; $module=~s/\.xdy\Z//; if ($language) { # map babel names to xindy names $language = $languagemap{$language} if ($languagemap{$language}); $langparam = "-L $language"; } else { $langparam = ""; # v2.10 initialise $language to suppress warnings $language = ""; } # most languages work with xindy's default codepage, but # some don't, so if the codepage isn't specified, check # the known cases that will generate an error # and supply a default. (For all other cases, it's up to the # user to supply a codepage.) # # Now that glossaries.sty v4.50 will default to 'utf8' if # \inputencodingname hasn't been defined or is empty, this also # needs to check 'utf8'. # v2.01 changed 'if ($codepage eq "")' to 'unless ($codepage)' # v4.50 changed to conditional to include encoding without modifiers if ($codepage eq '' or $codepage=~/^utf8|latin\d+|cp\d+$/) { if ($language eq 'dutch') { if ($codepage eq '') { $codepage = "ij-as-ij"; } else { $codepage = "ij-as-ij-$codepage"; } } elsif ($language eq 'german') { if ($codepage eq '') { $codepage = "din5007"; } else { $codepage = "din5007-$codepage"; } } elsif ($language eq 'gypsy') { if ($codepage eq '') { $codepage = "northrussian"; } else { $codepage = "northrussian-$codepage"; } } elsif ($language eq 'hausa' and $codepage eq '') { $codepage = "utf8"; } elsif ($language eq 'klingon' and $codepage eq '') { $codepage = "utf8"; } elsif ($language eq 'latin' and $codepage eq '') { $codepage = "utf8"; } elsif ($language eq 'mongolian') { if ($codepage eq '') { $codepage = "cyrillic"; } else { $codepage = "cyrillic-$codepage"; } } elsif ($language eq 'slovak') { if ($codepage eq '') { $codepage = "small"; } else { $codepage = "small-$codepage"; } } elsif ($language eq 'spanish') { if ($codepage eq '') { $codepage = "modern"; } else { $codepage = "modern-$codepage"; } } elsif ($language eq 'vietnamese') { if ($codepage eq '') { $codepage = "utf8"; } else { $codepage = "utf8-$codepage"; } } } elsif ($language eq 'german' and $codepage!~/(din5007|duden|braille)/) { #v2.16 added check for german din/duden #v4.50 now redundant? may be removed in the next version $codepage = "din5007-$codepage"; } my $codepageparam = ""; if ($codepage) { $codepageparam = "-C $codepage"; } $main = join(' ', "-I xindy", "-M \"$module\"", "-t \"$trans\"", "-o \"$out\"", "\"$in\""); $args = join(' ', $rest, $langparam, $codepageparam, $main); # v2.01 replaced code with call to &run_app my ($status, $warnings, $errno) = &run_app($app, $args, $trans, $quiet, $dontexec); return if ($dontexec); if ($status=~/Cannot locate xindy module for language ([^\s]+) in codepage ([^\s]+)/) { $args = join(' ', $rest, $langparam, $main); unless ($quiet) { my $message = "$&\nRetrying using default codepage.\n"; warn $message; $retry .= $message; } ($status, $warnings, $errno) = &run_app('xindy', $args, $trans, $quiet, $dontexec); } if ($status=~/Cannot locate xindy module for language ([^\s]+)/ and $1 ne 'general') { $args = join(' ', $rest, "-L general", $main); unless ($quiet) { my $message = "$&\nRetrying with -L general\n"; warn $message; $retry .= $message; } ($status, $warnings, $errno) = &run_app('xindy', $args, $trans, $quiet, $dontexec); } if ($errno) { # attempt further diagnostic my $diagnostic = ''; if ($status=~/index 0 should be less than the length of the string/m) { $diagnostic = $xindy_error_text{nosort}; $diagnostic .= &parse_for_xindy_nosort($in); } elsif ($status=~/variable .+ has no value/m) { # v2.19 fixed misspelt key $diagnostic = $xindy_error_text{istnotxdy}; } elsif ($status=~/Could not find file /m) { # v2.19 added $diagnostic = $xindy_error_text{noxdyfile}; } elsif ($status=~/Possible read-error due to ill-formed string " :sep/m) { $diagnostic = $xindy_error_text{missingendquote}; } elsif (not $language) { # If the language hasn't been set, then it may be # because the document doesn't contain # \printglossaries/\printglossary or it may be # because the user has a customized style file that # contains the language settings. $diagnostic = $xindy_error_text{nolanguage}; if ($in eq 'glo') { # or it may be that the user doesn't want to use the main # glossary and has forgotten to suppress it with the # "nomain" package option $diagnostic .= $xindy_error_text{nomain}; } } # v2.19 added: if ($diagnostic and open LOGFILE, ">>$trans") { print LOGFILE "\nmakeglossaries diagnostic messages:\n\n$diagnostic\n"; close LOGFILE; } die "\n***Call to xindy failed***\n", ($diagnostic ? "\nPossible cause of problem:\n\n". $diagnostic . "\n\n": "\n"), "Check '$trans' for details\n"; } # Check xindy warnings if ($status=~/^WARNING:/m) { my $diagwarn = ''; if ($status=~/did not match any location-class/m) { $diagwarn = $xindy_error_text{badlocation}; } if ($status=~/unknown attribute `pageglsnumberformat'/m) { $diagwarn .= $xindy_error_text{comp207}; } if ($status=~/Would replace complete index key by empty string, ignoring/m) { $diagwarn .= $xindy_error_text{nosort}; $diagwarn .= &parse_for_xindy_nosort($in); } if ($diagwarn) { warn "\n**Warning:**\n\n", $diagwarn, "\n"; $warnings .= "\nmakeglossaries diagnostic messages:\n\n" . $diagwarn; } } if ($retry or $warnings) { if (open LOGFILE, ">>$trans") { print LOGFILE "\n$warnings"; if ($retry) { print LOGFILE "\nmakeglossaries messages:\n\n", $retry; } close LOGFILE; } else { warn "Unable to open '$trans' $!\n"; } } } # v2.18 added: sub parse_for_xindy_nosort{ my ($in) = @_; my $msg = join("\n", "", "Attempting to determine which entries have problem sort keys.", "Parsing '$in'"); my %entries = (); if (open FD, $in) { while () { # v2.19 extra conditions added if (/:tkey \(\("((?:\\\\[a-zA-Z@]+ *)+)" "\\\\glossentry\{(.*)\}"\) \)/) { my $label = $2; unless ($entries{$label}) { $entries{$label}->{sort} = $label; $entries{$label}->{suggestion} = $label; $entries{$label}->{suggestion}=~s/([\\\s]+)//g; } } elsif (/:tkey \(\("(\\\\[a-zA-Z@]+ *{(?:\\\\[a-zA-Z@]+ *)+})+" "\\\\glossentry\{(.*)\}"\) \)/) { # name in the form \cs{\cs} my $label = $2; unless ($entries{$label}) { $entries{$label}->{sort} = $1; $entries{$label}->{suggestion} = $label; $entries{$label}->{suggestion}=~s/^[a-zA-Z]+[:\-\.]//; $entries{$label}->{suggestion}=~s/([^\w]+)//g; } } elsif (/:tkey \(\("(\$\s*(?:\{?\\\\[a-zA-Z@]+ *\}?)+\$)" "\\\\glossentry\{(.*)\}"\) \)/) { # name in the form $\cs$ my $label = $2; unless ($entries{$label}) { $entries{$label}->{sort} = $1; $entries{$label}->{suggestion} = $label; $entries{$label}->{suggestion}=~s/^[a-zA-Z]+[:\-\.]//; $entries{$label}->{suggestion}=~s/([^\w]+)//g; } } } close FD; my @labels = keys %entries; my $n = $#labels+1; if ($n == 0) { $msg .= "\nCouldn't find anything! Sorry, I'm stumped."; } else { $msg .= ($n == 1 ? "\n1 problematic entry found:\n" : "\n$n problematic entries found:\n"); foreach my $label (@labels) { my $thisentry = $entries{$label}; $msg .= "\nLabel: '$label'. Sort value : '$thisentry->{sort}'"; $msg .= "\n(Try adding sort={$thisentry->{suggestion}} to the definition.)"; } } } else { $msg .= "\nFailed to open '$in' $!\n"; } $msg; } sub HELP_MESSAGE{ print "\nSyntax : makeglossaries [options] \n\n"; print "For use with the glossaries package to pass relevant\n"; print "files to makeindex or xindy.\n\n"; print "\tBase name of glossary file(s). This should\n"; print "\t\tbe the name of your main LaTeX document without any\n"; print "\t\textension.\n"; print "\nGeneral Options:\n\n"; print "-o \tUse as the output file.\n"; print "\t\t(Don't use -o if you have more than one glossary.)\n"; print "-q\t\tQuiet mode.\n"; print "-Q\t\tSilence unable to fork warning.\n"; print "-k\t\tDon't try to use piped redirection.\n"; print "-l\t\tLetter ordering.\n"; print "-s \tEmploy as the style file.\n"; print "-t \tEmploy as the transcript file.\n"; print "\t\t(Don't use -t if you have more than one glossary\n"; print "\t\tor the transcripts will be overwritten.)\n"; print "-d \tDirectory in which the .aux, .glo etc files are located.\n", "\t\t(Default is the directory in which resides.)\n"; print "-n\t\tPrint the command that would normally be executed,\n", "\t\tbut don't execute it.\n"; print "\nXindy Options:\n\n"; print "-L \tUse .\n"; print "-x \tFull path to xindy executable.\n", "\t\t(Default assumes xindy is on the operating system's path.)\n"; print "\nMakeindex Options:\n"; print "(See makeindex documentation for further details on these "; print "options.)\n\n"; print "-c\t\tCompress intermediate blanks.\n"; print "-e\t\tDon't attempt to fix multiple encaps.\n"; print "-g\t\tEmploy German word ordering.\n"; print "-p \tSet the starting page number to be .\n"; print "-r\t\tDisable implicit page range formation.\n"; print "-m \tFull path to makeindex executable.\n", "\t\t(Default assumes makeindex is on the operating system's path.)\n"; } sub VERSION_MESSAGE{ my $verYear = ''; if ($version=~/(\d{4})-\d{2}-\d{2}/) { $verYear = "-$1"; } print "Makeglossaries Version $version\n"; print "Copyright (C) 2007$verYear Nicola L C Talbot\n"; print "This material is subject to the LaTeX Project Public License.\n"; } 1; =head1 NAME makeglossaries - Calls makeindex/xindy for LaTeX documents using glossaries package =head1 SYNOPSIS B [B<-o> I] [B<-q>] [B<-Q>] [B<-k>] [B<-n>] [B<-s> I] [B<-t> I] [B<-L> I] [B<-c>] [B<-g>] [B<-l>] [B<-p> I] [B<-r>] [B<-d> I] [B<-m> I] [B<-x> I] [B<-e>] [B<--version>] [B<--help>] I =head1 DESCRIPTION B is designed for use with LaTeX documents that use the glossaries package. The mandatory argument I should be the name of the LaTeX document without the .tex extension. B will read the auxiliary file to determine whether B or B should be called. All the information required to be passed to the relevant indexing application should also be contained in the auxiliary file, but may be overridden by the option arguments to B. =head1 OPTIONS =over 4 =item B<-e> Don't attempt to repair multiple encaps or illegal page number (B only). =item B<-c> Compress intermediate blanks (B only). =item B<-d> I Specify the directory the .aux, .glo etc files are located. Defaults to the parent directory of the base file I. =item B<-g> Employ German word ordering (B only). =item B<-k> Don't try forking with piped redirection. (Lessens the effect of -q) =item B<-l> Letter ordering. =item B<-L> I This option only has an effect if B is called. Sets the language. See B documentation for further details. =item B<-m> I Specify the full path name for B to I in the event that B isn't on the operating system's path. =item B<-n> Print the commands that would normally be executed but don't run them. =item B<-o> I Use I as the output file. (Only suitable for documents containing a single glossary, otherwise each glossary will be overridden.) =item B<-p> I Sets the starting page number to be I (B only). =item B<-q> Quiet mode. Reduces chatter to standard output. =item B<-Q> Silences warning about being unable to fork. =item B<-r> =item B<-s> I Use I as the style file. Note that if you use this option, you need to know whether B or B will be called, as they have different style files. =item B<-t> I Use I as the transcript file. Disable implicit page range formation (B only). =item B<-x> I Specify the full path name for B to I in the event that B isn't on the operating system's path. =item B<--version> Prints version number and exits. =item B<--help> Prints help message and exits. =back =head1 REQUIRES Perl, Getopt::Std, and makeindex or xindy (depending on glossaries package options). =head1 LICENSE This is free software distributed under the LaTeX Project Public License. There is NO WARRANTY. See L for details. =head1 AUTHOR Nicola L. C. Talbot, L =head1 RECOMMENDED READING The glossaries manual: texdoc glossaries =cut