# autolatex - Make.pm
# Copyright (C) 2013-2016  Stephane Galland <[email protected]>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; see the file COPYING.  If not, write to
# the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
# Boston, MA 02111-1307, USA.

=pod

=head1 NAME

Make.pm - Make-like Tools

=head1 DESCRIPTION

Provides tools that are close to the Make tool.

To use this library, type C<use AutoLaTeX::Make::Make;>.

=head1 GETTING STARTED

=head2 Initialization

To create a Make tool, say something like this:

   use AutoLaTeX::Make::Make;

   my $make = AutoLaTeX::Make::Make->new($configuration) ;

..or something similar. Acceptable parameters to the constructor are:

=over

=item * C<configuration> is an associative array that contains all the configuration of AutoLaTeX.

=back

=head1 METHOD DESCRIPTIONS

This section contains only the methods in TeXParser.pm itself.

=over

=cut
package AutoLaTeX::Make::Make;

our @ISA = qw( Exporter );
our @EXPORT = qw( );
our @EXPORT_OK = qw();

require 5.014;
use strict;
use utf8;
use vars qw(@ISA @EXPORT @EXPORT_OK $VERSION);
use Exporter;
use Class::Struct;
use File::Basename;
use File::Spec;
use Carp;

use AutoLaTeX::Core::Util;
use AutoLaTeX::Core::IntUtils;
use AutoLaTeX::Core::Config;
use AutoLaTeX::Core::OS;
use AutoLaTeX::Core::Progress;
use AutoLaTeX::TeX::BibCitationAnalyzer;
use AutoLaTeX::TeX::TeXDependencyAnalyzer;
use AutoLaTeX::TeX::IndexAnalyzer;
use AutoLaTeX::TeX::GlossaryAnalyzer;

our $VERSION = '34.0';

my $EXTENDED_WARNING_CODE = <<'ENDOFTEX';
       %*************************************************************
       % CODE ADDED BY AUTOLATEX TO CHANGE THE OUPUT OF THE WARNINGS
       %*************************************************************
       \makeatletter
       \newcount\autolatex@@@lineno
       \newcount\autolatex@@@lineno@delta
       \xdef\autolatex@@@mainfile@real{::::REALFILENAME::::}
       \def\autolatex@@@mainfile{autolatex_autogenerated.tex}
       \xdef\autolatex@@@filename@stack{{\autolatex@@@mainfile}{\autolatex@@@mainfile}}
       \global\let\autolatex@@@currentfile\autolatex@@@mainfile
       \def\autolatex@@@filename@stack@push#1{%
               \xdef\autolatex@@@filename@stack{{#1}\autolatex@@@filename@stack}%
       }
       \def\autolatex@@@filename@stack@pop@split#1#2\@nil{%
               \gdef\autolatex@@@currentfile{#1}%
               \gdef\autolatex@@@filename@stack{#2}%
       }
       \def\autolatex@@@filename@stack@pop{%
               \expandafter\autolatex@@@filename@stack@pop@split\autolatex@@@filename@stack\@nil}
       \def\autolatex@@@update@filename{%
               \ifx\autolatex@@@mainfile\autolatex@@@currentfile%
                       \edef\autolatex@@@warning@filename{\autolatex@@@mainfile@real}%
                       \global\autolatex@@@lineno@delta=::::AUTOLATEXHEADERSIZE::::\relax%
               \else%
                       \edef\autolatex@@@warning@filename{\autolatex@@@currentfile}%
                       \global\autolatex@@@lineno@delta=0\relax%
               \fi%
               {\filename@parse{\autolatex@@@warning@filename}\global\let\autolatex@@@filename@ext\filename@ext}%
               \xdef\autolatex@@@generic@warning@beginmessage{!!!![BeginWarning]\autolatex@@@warning@filename:\ifx\autolatex@@@filename@ext\relax.tex\fi:}%
               \xdef\autolatex@@@generic@warning@endmessage{!!!![EndWarning]\autolatex@@@warning@filename}%
       }
       \def\autolatex@@@openfile#1{%
               \expandafter\autolatex@@@filename@stack@push{\autolatex@@@currentfile}%
               \xdef\autolatex@@@currentfile{#1}%
               \autolatex@@@update@filename%
       }
       \def\autolatex@@@closefile{%
               \autolatex@@@filename@stack@pop%
               \autolatex@@@update@filename%
       }
       \let\autolatex@@@InputIfFileExists\InputIfFileExists
       \long\def\InputIfFileExists#1#2#3{%
               \autolatex@@@openfile{#1}%
               \autolatex@@@InputIfFileExists{#1}{#2}{#3}%
               \autolatex@@@closefile%
       }
       \let\autolatex@@@input\@input
       \long\def\@input#1{%
               \autolatex@@@openfile{#1}%
               \autolatex@@@input{#1}%
               \autolatex@@@closefile%
       }
       \global\DeclareRobustCommand{\GenericWarning}[2]{%
               \global\autolatex@@@lineno\inputlineno\relax%
               \global\advance\autolatex@@@lineno\autolatex@@@lineno@delta\relax%
               \begingroup
               \def\MessageBreak{^^J#1}%
               \set@display@protect
               \immediate\write\@unused{^^J\autolatex@@@generic@warning@beginmessage\the\autolatex@@@lineno: #2\on@line.^^J\autolatex@@@generic@warning@endmessage^^J}%
               \endgroup
       }
       \autolatex@@@update@filename
       \makeatother
       %*************************************************************
ENDOFTEX

my %COMMAND_DEFINITIONS = (
       'pdflatex' => {
               'cmd' => 'pdflatex',
               'flags' => ['-halt-on-error', '-interaction', 'batchmode', '-file-line-error'],
               'to_dvi' => ['-output-format=dvi'],
               'to_ps' => undef,
               'to_pdf' => ['-output-format=pdf'],
               'synctex' => '-synctex=1',
               'jobname' => '-jobname',
               'ewarnings' => $EXTENDED_WARNING_CODE,
       },
       'latex' => {
               'cmd' => 'latex',
               'flags' => ['-halt-on-error', '-interaction', 'batchmode', '-file-line-error'],
               'to_dvi' => ['-output-format=dvi'],
               'to_ps' => undef,
               'to_pdf' => ['-output-format=pdf'],
               'synctex' => '-synctex=1',
               'jobname' => '-jobname',
               'ewarnings' => $EXTENDED_WARNING_CODE,
       },
       'xelatex' => {
               'cmd' => 'xelatex',
               'flags' => ['-halt-on-error', '-interaction', 'batchmode', '-file-line-error'],
               'to_dvi' => ['-no-pdf'],
               'to_ps' => undef,
               'to_pdf' => [],
               'synctex' => '-synctex=1',
               'jobname' => '-jobname',
               'ewarnings' => $EXTENDED_WARNING_CODE,
       },
       'lualatex' => {
               'cmd' => 'lualatex',
               'flags' => ['-halt-on-error', '-interaction', 'batchmode', '-file-line-error'],
               'to_dvi' => ['-output-format=dvi'],
               'to_ps' => undef,
               'to_pdf' => ['-output-format=pdf'],
               'synctex' => '-synctex=1',
               'jobname' => '-jobname',
               'ewarnings' => $EXTENDED_WARNING_CODE,
       },
       'bibtex' => {
               'cmd' => 'bibtex',
               'flags' => [],
       },
       'biber' => {
               'cmd' => 'biber',
               'flags' => [],
       },
       'makeindex' => {
               'cmd' => 'makeindex',
               'flags' => [],
       },
       'makeglossaries' => {
               'cmd' => 'makeglossaries',
               'flags' => [],
       },
       'dvi2ps' => {
               'cmd' => 'dvips',
               'flags' => [],
       },
);

struct( Entry => [
               'file' => '$',
               'go_up' => '$',
               'rebuild' => '$',
               'parent' => '$',
] );

sub newEntry($$) {
       my $e = Entry->new;
       @$e = ($_[0],0,0,$_[1]);
       return $e;
}

#------------------------------------------------------
#
# Constructor
#
#------------------------------------------------------

sub new(\%) : method {
       my $proto = shift;
       my $class = ref($proto) || $proto;
       my $parent = ref($proto) && $proto ;

       my $self ;
       if ( $parent ) {
               %{$self} = %{$parent} ;
       }
       else {
               $self = {
                       'configuration' => $_[0],
                       'files' => {},
                       'rootFiles' => [],
                       'is_extended_warning_enable' => 0,
                       'is_biblio_enable' => 1,
                       'is_makeindex_enable' => 1,
                       'is_makeglossaries_enable' => 1,
                       'warning_level' => 1,
                       'generation_type' => 'pdf',
                       'latex_cmd' => [],
                       'bibtex_cmd' => [],
                       'biber_cmd' => [],
                       'makeindex_cmd' => [],
                       'makeglossaries_cmd' => [],
                       'dvi2ps_cmd' => [],
               };
       }
       bless( $self, $class );

       #
       # Build the different commands according to the current configuration
       #
       $self->{'type'} = $_[0]->{'generation.generation type'} || 'pdf';
       my $compiler = $_[0]->{'generation.tex compiler'} || 'pdflatex';
       $self->{'compiler_definition'} = $COMMAND_DEFINITIONS{"$compiler"};
       my $def = $self->{'compiler_definition'};
       confess("No command definition for '$compiler'") unless ($def);

       # LaTeX
       if ($_[0]->{'generation.latex_cmd'}) {
               push @{$self->{'latex_cmd'}}, $_[0]->{'generation.latex_cmd'};
       }
       else {
               push @{$self->{'latex_cmd'}}, $def->{'cmd'}, @{$def->{'flags'}};
               confess("No command definition for '$compiler/".$self->{'type'}."'") unless (exists $def->{'to_'.$self->{'type'}});

               # Support of SyncTeX
               if (cfgBoolean($_[0]->{'generation.synctex'}) && $def->{'synctex'}) {
                       push @{$self->{'latex_cmd'}}, $def->{'synctex'};
               }

               my $target = $def->{'to_'.$self->{'type'}};
               if (defined($target)) {
                       push @{$self->{'latex_cmd'}}, @{$target};
               }
               elsif ($self->{'type'} eq 'ps') {
                       push @{$self->{'latex_cmd'}}, @{$def->{'to_dvi'}};
               }
               else {
                       confess('invalided Maker state: cannot find the command line to compile TeX files.');
               }
       }

       if ($_[0]->{'generation.latex_flags'}) {
               my @params = split(/\s+/, ($_[0]->{'generation.latex_flags'}));
               push @{$self->{'latex_cmd'}}, @params;
       }

       # Change the warning level
       if (defined($_[0]->{'__private__'}{'CLI.warning level'})) {
               $self->{'warning_level'} = int($_[0]->{'__private__'}{'CLI.warning level'});
       }

       # Support of extended warnings
       if (($_[0]->{'__private__'}{'CLI.is extended tex warnings'} || ($self->{'warning_level'}>1))
           && $def->{'ewarnings'}) {
               my $code = $def->{'ewarnings'} || '';
               $code =~ s/^\s+//gm;
               $code =~ s/\s+$//gm;
               my $s = - countLinesIn($code);
               $code =~ s/\Q::::AUTOLATEXHEADERSIZE::::\E/$s/sg;
               $self->{'latex_warning_code'} = $code;
               $self->{'is_extended_warning_enable'} = 1;
       }

       # BibTeX
       if ($_[0]->{'generation.bibtex_cmd'}) {
               push @{$self->{'bibtex_cmd'}}, $_[0]->{'generation.bibtex_cmd'};
       }
       else {
               $def = $COMMAND_DEFINITIONS{'bibtex'};
               confess("No command definition for 'bibtex'") unless ($def);
               push @{$self->{'bibtex_cmd'}}, $def->{'cmd'}, @{$def->{'flags'}};
       }

       if ($_[0]->{'generation.bibtex_flags'}) {
               my @params = split(/\s+/, ($_[0]->{'generation.bibtex_flags'}));
               push @{$self->{'bibtex_cmd'}}, @params;
       }

       # Biber
       if ($_[0]->{'generation.biber_cmd'}) {
               push @{$self->{'biber_cmd'}}, $_[0]->{'generation.biber_cmd'};
       }
       else {
               $def = $COMMAND_DEFINITIONS{'biber'};
               confess("No command definition for 'biber'") unless ($def);
               push @{$self->{'biber_cmd'}}, $def->{'cmd'}, @{$def->{'flags'}};
       }

       if ($_[0]->{'generation.biber_flags'}) {
               my @params = split(/\s+/, ($_[0]->{'generation.biber_flags'}));
               push @{$self->{'biber_cmd'}}, @params;
       }

       # MakeIndex
       if ($_[0]->{'generation.makeindex_cmd'}) {
               push @{$self->{'makeindex_cmd'}}, $_[0]->{'generation.makeindex_cmd'};
       }
       else {
               $def = $COMMAND_DEFINITIONS{'makeindex'};
               confess("No command definition for 'makeindex'") unless ($def);
               push @{$self->{'makeindex_cmd'}}, $def->{'cmd'}, @{$def->{'flags'}};
       }

       if ($_[0]->{'generation.makeindex_flags'}) {
               my @params = split(/\s+/, ($_[0]->{'generation.makeindex_flags'}));
               push @{$self->{'makeindex_cmd'}}, @params;
       }

       # MakeGlossaries
       if ($_[0]->{'generation.makeglossaries_cmd'}) {
               push @{$self->{'makeglossaries_cmd'}}, $_[0]->{'generation.makeglossaries_cmd'};
       }
       else {
               $def = $COMMAND_DEFINITIONS{'makeglossaries'};
               confess("No command definition for 'makeglossaries'") unless ($def);
               push @{$self->{'makeglossaries_cmd'}}, $def->{'cmd'}, @{$def->{'flags'}};
       }

       if ($_[0]->{'generation.makeglossaries_flags'}) {
               my @params = split(/\s+/, ($_[0]->{'generation.makeglossaries_flags'}));
               push @{$self->{'makeglossaries_cmd'}}, @params;
       }

       # dvi2ps
       if ($_[0]->{'generation.dvi2ps_cmd'}) {
               push @{$self->{'dvi2ps_cmd'}}, $_[0]->{'generation.dvi2ps_cmd'};
       }
       else {
               $def = $COMMAND_DEFINITIONS{'dvi2ps'};
               confess("No command definition for 'dvi2ps'") unless ($def);
               push @{$self->{'dvi2ps_cmd'}}, $def->{'cmd'}, @{$def->{'flags'}};
       }

       if ($_[0]->{'generation.dvi2ps_flags'}) {
               my @params = split(/\s+/, ($_[0]->{'generation.dvi2ps_flags'}));
               push @{$self->{'dvi2ps_cmd'}}, @params;
       }

       return $self;
}

=pod

=item * makeRelativePath($)

Make the path relative to the current directory.

=cut
sub makeRelativePath($) : method {
       my $self = shift;
       my $relativePath = File::Spec->abs2rel($_[0]);
       return $relativePath;
}


=pod

=item * reset()

Reset this make tool.

=cut
sub reset() : method {
       my $self = shift;
       $self->{'files'} = {};
       $self->{'rootFiles'} = [];
       return undef;
}

=pod

=item * addTeXFile($)

Add the given TeX file into the building process.
Takes 1 arg:

=over

=item * file (string)

is the name of the TeX file to read.

=back

=cut
sub addTeXFile($) : method {
       my $self = shift;
       my $rootfile = shift;
       $rootfile = File::Spec->rel2abs($rootfile);
       my $rootbasename = basename($rootfile, '.tex');
       my $roottemplate = File::Spec->catfile(dirname($rootfile), "$rootbasename");

       my $pdfFile = "$roottemplate.pdf";
       $self->{'files'}{$pdfFile} = {
               'type' => 'pdf',
               'dependencies' => { $rootfile => undef },
               'change' => lastFileChange($pdfFile),
               'mainFile' => $rootfile,
       };
       push @{$self->{'rootFiles'}}, "$pdfFile";

       return undef;
}

sub _computeDependenciesForRootFile($) : method {
       my $self = shift;
       my $pdfFile = shift;
       my $rootfile = $self->{'files'}{$pdfFile}{'mainFile'};
       my $rootdir = dirname($rootfile);
       my $rootbasename = basename($rootfile, '.tex');
       my $roottemplate = File::Spec->catfile(dirname($rootfile), "$rootbasename");

       my @files = ( $rootfile );
       while (@files) {
               my $file = shift @files;
               printDbgFor(2, formatText(_T("Parsing '{}'"), $file));
               if (-f "$file" ) {
                       printDbgIndent();
                       printDbgFor(3, formatText(_T("Adding file '{}'"), removePathPrefix($rootdir,$file)));
                       $self->{'files'}{$file} = {
                               'type' => 'tex',
                               'dependencies' => {},
                               'change' => lastFileChange($file),
                       };
                       my %deps = getDependenciesOfTeX($file,$rootdir);
                       if (%deps) {
                               my $dir = dirname($file);

                               #
                               # INCLUDED FILES
                               #
                               foreach my $cat ('tex', 'sty', 'cls') {
                                       if ($deps{$cat}) {
                                               foreach my $dpath (@{$deps{$cat}}) {
                                                       if (!File::Spec->file_name_is_absolute($dpath)) {
                                                               $dpath = File::Spec->catfile($dir, $dpath);
                                                       }
                                                       if ($dpath !~ /\.$cat/) {
                                                               $dpath .= ".$cat";
                                                       }
                                                       printDbgFor(3, formatText(_T("Adding file '{}'"), removePathPrefix($rootdir,$dpath)));
                                                       $self->{'files'}{$dpath} = {
                                                               'type' => $cat,
                                                               'dependencies' => {},
                                                               'change' => lastFileChange($dpath),
                                                       };
                                                       $self->{'files'}{$pdfFile}{'dependencies'}{$dpath} = undef;
                                                       if ($cat eq 'tex') {
                                                               push @files, $dpath;
                                                       }
                                               }
                                       }
                               }

                               #
                               # BIBLIOGRAPHY CALLED FROM THE TEX
                               #
                               if ($deps{'biblio'}) {
                                       while (my ($bibdb,$bibdt) = each(%{$deps{'biblio'}})) {
                                               my $dir = dirname($file);
                                               if ($rootdir ne $dir) {
                                                       $bibdb = $rootbasename;
                                               }
                                               my $bblfile = File::Spec->catfile("$rootdir", "$bibdb.bbl");
                                               printDbgFor(3, formatText(_T("Adding file '{}'"), removePathPrefix($rootdir,$bblfile)));
                                               $self->{'files'}{"$bblfile"} = {
                                                       'type' => 'bbl',
                                                       'dependencies' => {},
                                                       'change' => lastFileChange("$bblfile"),
                                                       'use_biber' => $deps{'biber'},
                                               };
                                               $self->{'files'}{$pdfFile}{'dependencies'}{$bblfile} = undef;
                                               foreach my $cat ('bib', 'bst', 'bbc', 'cbx') {
                                                       if ($bibdt->{$cat}) {
                                                               foreach my $dpath (@{$bibdt->{$cat}}) {
                                                                       if (!File::Spec->file_name_is_absolute($dpath)) {
                                                                               $dpath = File::Spec->catfile("$rootdir", $dpath);
                                                                       }
                                                                       if ($dpath !~ /\.$cat/) {
                                                                               $dpath .= ".$cat";
                                                                       }
                                                                       printDbgFor(3, formatText(_T("Adding file '{}'"), removePathPrefix($rootdir,$dpath)));
                                                                       $self->{'files'}{$dpath} = {
                                                                               'type' => $cat,
                                                                               'dependencies' => {},
                                                                               'change' => lastFileChange($dpath),
                                                                       };
                                                                       $self->{'files'}{"$bblfile"}{'dependencies'}{$dpath} = undef;
                                                               }
                                                       }
                                               }
                                       }
                               }

                               #
                               # INDEX
                               #
                               if ($deps{'idx'}) {
                                       for my $idxdep (@{$deps{'idx'}}) {
                                               my $idxbasefilename;
                                               if ($idxdep) {
                                                       $idxbasefilename = "$idxdep";
                                               } else {
                                                       $idxbasefilename = "$roottemplate";
                                               }
                                               my $idxfile = "$idxbasefilename.idx";
                                               printDbgFor(3, formatText(_T("Adding file '{}'"), removePathPrefix($rootdir,$idxfile)));
                                               $self->{'files'}{"$idxfile"} = {
                                                       'type' => 'idx',
                                                       'dependencies' => {},
                                                       'change' => lastFileChange("$idxfile"),
                                               };
                                               my $indfile = "$idxbasefilename.ind";
                                               printDbgFor(3, formatText(_T("Adding file '{}'"), removePathPrefix($rootdir,$indfile)));
                                               $self->{'files'}{"$indfile"} = {
                                                       'type' => 'ind',
                                                       'dependencies' => { $idxfile => undef },
                                                       'change' => lastFileChange("$indfile"),
                                               };
                                               $self->{'files'}{$pdfFile}{'dependencies'}{$indfile} = undef;
                                       }
                               }

                               #
                               # GLOSSARIES
                               #
                               if ($deps{'glo'}) {
                                       my $glofile = "$roottemplate.glo";
                                       printDbgFor(3, formatText(_T("Adding file '{}'"), removePathPrefix($rootdir,$glofile)));
                                       $self->{'files'}{"$glofile"} = {
                                               'type' => 'glo',
                                               'dependencies' => {},
                                               'change' => lastFileChange("$glofile"),
                                       };
                                       my $glsfile = "$roottemplate.gls";
                                       printDbgFor(3, formatText(_T("Adding file '{}'"), removePathPrefix($rootdir,$glsfile)));
                                       $self->{'files'}{"$glsfile"} = {
                                               'type' => 'gls',
                                               'dependencies' => { $glofile => undef },
                                               'change' => lastFileChange("$glsfile"),
                                       };
                                       $self->{'files'}{$pdfFile}{'dependencies'}{$glsfile} = undef;
                               }
                       }
                       printDbgUnindent();
               }
       }

       printDbgFor(2, formatText(_T("Parsing auxiliary files")));
       printDbgIndent();

       #
       # BIBLIOGRAPHY FROM INSIDE AUXILIARY FILES (MULTIBIB...)
       #
       local *DIR;
       opendir(*DIR, "$rootdir") or printErr("$rootdir: $!");
       while (my $dir = readdir(*DIR)) {
               if ((!isIgnorableDirectory($dir)) && $dir =~ /^(.+?)\.aux$/) {
                       my $bibdb = "$1";
                       if ($bibdb ne "$rootbasename") {
                               my $auxfile = File::Spec->catfile("$rootdir", "$dir");
                               my %data = getAuxBibliographyData("$auxfile");
                               if ($data{'databases'} || $data{'styles'}) {
                                       my $bblfile = File::Spec->catfile("$rootdir", "$bibdb.bbl");
                                       printDbgFor(3, formatText(_T("Adding file '{}'"), removePathPrefix($rootdir,$bblfile)));
                                       $self->{'files'}{"$bblfile"} = {
                                               'type' => 'bbl',
                                               'dependencies' => {},
                                               'change' => lastFileChange("$bblfile"),
                                       };
                                       $self->{'files'}{$pdfFile}{'dependencies'}{$bblfile} = undef;
                                       if ($data{'styles'}) {
                                               foreach my $style (@{$data{'styles'}}) {
                                                       my $bstfile = File::Spec->catfile("$rootdir", "$style.bst");
                                                       if (-r "$bstfile") {
                                                               printDbgFor(3, formatText(_T("Adding file '{}'"), removePathPrefix($rootdir,$bstfile)));
                                                               $self->{'files'}{"$bstfile"} = {
                                                                       'type' => 'bst',
                                                                       'dependencies' => {},
                                                                       'change' => lastFileChange("$bstfile"),
                                                               };
                                                               $self->{'files'}{$bblfile}{'dependencies'}{$bstfile} = undef;
                                                       }
                                               }
                                       }
                                       if ($data{'databases'}) {
                                               foreach my $db (@{$data{'databases'}}) {
                                                       my $bibfile = File::Spec->catfile("$rootdir", "$db.bib");
                                                       if (-r "$bibfile") {
                                                               printDbgFor(3, formatText(_T("Adding file '{}'"), removePathPrefix($rootdir,$bibfile)));
                                                               $self->{'files'}{"$bibfile"} = {
                                                                       'type' => 'bib',
                                                                       'dependencies' => {},
                                                                       'change' => lastFileChange("$bibfile"),
                                                               };
                                                               $self->{'files'}{$bblfile}{'dependencies'}{$bibfile} = undef;
                                                       }
                                               }
                                       }
                               }
                       }
               }
       }
       closedir(*DIR);

       printDbgUnindent();

       return undef;
}

=pod

=item * runLaTeX()

Launch pdfLaTeX once time.

=over 4

=item * C<file> is the name of the PDF or the TeX file to compile.

=item * C<enableLoop> (optional boolean) indicates if this function may loop on the LaTeX compilation when it is requested by the LaTeX tool.

=item * C<buffering_warnings> (optional boolean) indicates if the warnings are buffered or not.

=back

=cut
sub runLaTeX($;$$) : method {
       my $self = shift;
       my $file = shift;
       my $linenumber = 0;
       my $enableLoop = shift;
       my $buffering_warnings = shift;
       if ($self->{'files'}{$file}{'mainFile'}) {
               $file = $self->{'files'}{$file}{'mainFile'};
       }
       my $logFile = File::Spec->catfile(dirname($file), basename($file, '.tex').'.log');
       my $minNumberOfLaunchs = $self->{'configuration'}{'generation.post compilation runs'} || 1;
       my $numberOfRuns = 0;
       my $continueToCompile;
       do {
               printDbg(formatText(_T('{}: {}'), 'PDFLATEX', basename($file)));
               $continueToCompile = 0;
               $self->{'buffered_warnings'} = [];
               $self->{'warnings'} = {};
               unlink($logFile);
               my $exitcode;
               if ($self->{'is_extended_warning_enable'}) {
                       local *OUTFILE;
                       open(*OUTFILE, ">autolatex_autogenerated.tex") or printErr("autolatex_ewarnings.tex: $!");
                       my $code = $self->{'latex_warning_code'};
                       $code =~ s/\Q::::REALFILENAME::::\E/$file/sg;
                       print OUTFILE $code."\n";
                       print OUTFILE readFileLines($file);
                       close(*OUTFILE);
                       $exitcode = runCommandSilently(
                               @{$self->{'latex_cmd'}},
                               $self->{'compiler_definition'}{'jobname'},
                               basename($file, '.tex'),
                               'autolatex_autogenerated.tex');
                       unlink('autolatex_autogenerated.tex') if ($exitcode==0);
               }
               else {
                       $exitcode = runCommandSilently(@{$self->{'latex_cmd'}},
                               $self->makeRelativePath($file));
               }

               local *LOGFILE;

               if ($exitcode!=0) {
                       printDbg(formatText(_T("{}: Error when processing {}"), 'PDFLATEX', basename($file)));

                       # Parse the log to extract the blocks of messages
                       my $line;
                       my $fatal_error = undef;
                       my @log_blocks = ();
                       my $current_log_block = '';
                       my $re_fatal_error = "\Q==>\E\\s*f\\s*a\\s*t\\s*a\\s*l\\s+e\\s*r\\s*r\\s*o\\s*r";
                       open(*LOGFILE, "< $logFile") or printErr("$logFile: $!");
                       while (*LOGFILE && ($line = <LOGFILE>) && !$fatal_error) {
                               my $is_empty_line = (!$line || $line =~/^\s*$/);
                               if ($is_empty_line) {
                                       # Empty line => break the block
                                       if ($current_log_block) {
                                           if ($current_log_block =~ /^(.+):([0-9]+):/m) {
                                               if ($current_log_block =~ /^\!\s*$re_fatal_error/si) {
                                                       $fatal_error = "\Q!\E[^!]";
                                               }
                                               elsif ($current_log_block =~ /^(.+:[0-9]+:)\s*$re_fatal_error/si) {
                                                       $fatal_error = $1;
                                               }
                                               else {
                                                       push @log_blocks, $current_log_block;
                                               }
                                           }
                                           elsif ($current_log_block =~ /^\!/m) {
                                               push @log_blocks, $current_log_block;
                                           }
                                       }
                                       $current_log_block = '';
                               }
                               else {
                                       $current_log_block .= $line;
                               }
                       }
                       close(*LOGFILE);
                       if ($current_log_block) {
                           if ($current_log_block =~ /^.+:[0-9]+:/m) {
                               if ($current_log_block =~ /^\!\s*$re_fatal_error/si) {
                                       $fatal_error = "\Q!\E[^!]";
                               }
                               elsif ($current_log_block =~ /^(.+:[0-9]+:)\s*$re_fatal_error/si) {
                                       $fatal_error = $1;
                               }
                               else {
                                       push @log_blocks, $current_log_block;
                               }
                           }
                           elsif ($current_log_block =~ /^\!/m) {
                               if ($current_log_block =~ /^\!\s*$re_fatal_error/si) {
                                       $fatal_error = "\Q!\E[^!]";
                               }
                               else {
                                       push @log_blocks, $current_log_block;
                               }
                           }
                       }
                       # Search the fatal error inside the blocks
                       my $extracted_message = '';
                       if ($fatal_error) {
                               # Parse the fatal error block to extract the filename
                               # where the error occured
                               if ($fatal_error =~ /^(.+?)\:([0-9]+)\:$/s) {
                                       my ($candidate, $post) = ($1,$2);
                                       my @candidates = split(/[\n\r]+/, $candidate);
                                       $candidate = pop @candidates;
                                       my $candidate_pattern = "\Q$candidate\E";
                                       while ($candidate && @candidates && ! -f "$candidate") {
                                               my $l = pop @candidates;
                                               $candidate_pattern = "\Q$l\E[\n\r]+$candidate_pattern";
                                               $candidate = $l.$candidate;
                                       }
                                       if ($candidate && -e "$candidate") {
                                               $linenumber = int($post);
                                               # Search the error message in the log.
                                               $candidate_pattern .= "\Q:$post:\E";
                                               # Filtering the 'autogenerated' file
                                               if ($self->{'is_extended_warning_enable'} &&
                                                   basename($candidate) eq 'autolatex_autogenerated.tex') {
                                                       my $code = $self->{'latex_warning_code'};
                                                       $candidate = $file;
                                                       $linenumber -= countLinesIn($code);
                                               }
                                               my $i = 0;
                                               while (!$extracted_message && $i<@log_blocks) {
                                                       my $block = $log_blocks[$i];
                                                       if ($block =~ /$candidate_pattern(.*)$/s) {
                                                               my $message_text = $1 || '';
                                                               $extracted_message = trim($candidate.':'.$linenumber.':'.$message_text);
                                                       }
                                                       $i++;
                                               }
                                               if ($extracted_message) {
                                                       if (int($post)!=$linenumber) {
                                                               $extracted_message =~ s/^\Ql.$post\E/l.$linenumber/gm;
                                                       }
                                                       # Do not cut the words with carriage returns
                                                       $extracted_message =~ s/([a-z])[\n\r\f]([a-z])/$1$2/sgi;
                                                       $extracted_message =~ s/([a-z]) [\n\r\f]([a-z])/$1 $2/sgi;
                                                       $extracted_message =~ s/([a-z])[\n\r\f] ([a-z])/$1 $2/sgi;
                                               }
                                       }
                               }
                               else {
                                       # Search the error message in the log.
                                       my $candidate_pattern .= "$fatal_error";
                                       my $i = 0;
                                       while (!$extracted_message && $i<@log_blocks) {
                                               my $block = $log_blocks[$i];
                                               if ($block =~ /(?:^|\n|\r)$candidate_pattern\s*(.*)$/s) {
                                                       my $message = $1;
                                                       $linenumber = 0;
                                                       if ($message =~ /line\s+([0-9]+)/i) {
                                                               $linenumber = int($1);
                                                       }
                                                       $extracted_message = trim("$file:$linenumber: $message");
                                               }
                                               $i++;
                                       }
                               }
                       }

                       # Display the message
                       if ($extracted_message) {

                               # Test if the message is an emergency stop
                               if ($extracted_message =~ /^.*?:[0-9]+:\s*emergency\s+stop\./i) {
                                       foreach my $block (@log_blocks) {
                                               if ($block =~ /^\s*!\s*(.*?)\s*$/s) {
                                                       my $errmsg = "$1";
                                                       $extracted_message .= "\n$errmsg";
                                               }
                                       }
                               }

                               printDbg(formatText(_T("{}: The first error found in the log file is:"), 'PDFLATEX'));
                               print STDERR "$extracted_message\n";
                               printDbg(formatText(_T("{}: End of error log."), 'PDFLATEX'));
                       }
                       else {
                               print STDERR (formatText(_T("{}: Unable to extract the error from the log. Please read the log file."), 'PDFLATEX'))."\n";
                       }

                       exit(255);
               }
               elsif ($enableLoop) {
                       $numberOfRuns ++;
                       if ($numberOfRuns < $minNumberOfLaunchs) {
                               # Force a new run of the LaTeX tool.
                               printDbg(formatText(_T('{}: Forcing a new launch to reach {} on {}'), 'PDFLATEX', ($numberOfRuns + 1), $minNumberOfLaunchs));
                               $continueToCompile = 1;
                       }
                       else {
                               ($continueToCompile,$enableLoop) = $self->_testLaTeXWarningInFile(
                                       $logFile,$continueToCompile,$enableLoop);
                       }
               }
       }
       while ($continueToCompile);

       if (!$buffering_warnings && $self->{'buffered_warnings'}) {
               foreach my $w (@{$self->{'buffered_warnings'}}) {
                       print STDERR "$w";
               }
               $self->{'buffered_warnings'} = [];
       }

       return 0;
}

sub _printWarning($$$$) : method {
       my $self = shift;
       my $filename = shift;
       my $extension = shift;
       my $line = shift;
       my $message = shift;
       if ($message =~ /^\s*latex\s+warning\s*\:\s*(.*)$/i) {
               $message = "$1";
       }
       if (!$self->{'buffered_warnings'}) {
               $self->{'buffered_warnings'} = [];
       }
       push @{$self->{'buffered_warnings'}},
               "$filename:$line:warning: $message\n";
}

sub _testLaTeXWarningInFile($$$) : method {
       my $self = shift;
       my $logFile = shift;
       my $continueToCompile = shift;
       my $enableLoop = shift;
       my $line;
       my $warning = 0;
       open(*LOGFILE, "< $logFile") or printErr("$logFile: $!");
       my $lastline = '';
       my $current_log_block = '';
       while (!$continueToCompile && ($line = <LOGFILE>)) {
               $lastline .= $line;
               if ($lastline =~ /\.\s*$/) {
                       if ($self->_testLaTeXWarningOn($lastline)) {
                               $continueToCompile = $enableLoop;
                       }
                       $lastline = '';
               }
               # Parse and output the detailled warning messages
               if ($self->{'warning_level'}>1) {
                       if ($warning) {
                               if ($line =~ /^\Q!!!![EndWarning]\E/) {
                                       if ($current_log_block =~ /^(.*?):([^:]*):([0-9]+):\s*(.*?)\s*$/) {
                                               my ($filename, $extension, $line, $message) = ($1, $2, $3, $4);
                                               $self->_printWarning($filename, $extension, $line, $message);
                                       }
                                       $warning = 0;
                                       $current_log_block = '';
                               }
                               else {
                                       my $l = $line;
                                       if ($l !~ /\.\n+$/) {
                                               $l =~ s/\s+//;
                                       }
                                       $current_log_block .= $l;
                               }
                       }
                       elsif ($line =~ /^\Q!!!![BeginWarning]\E(.*)$/) {
                               my $l = "$1";
                               if ($l !~ /\.\n+$/) {
                                       $l =~ s/\s+//;
                               }
                               $current_log_block = $l;
                               $warning = 1;
                       }
               }
       }
       if ($lastline =~ /\.\s*$/ && $self->_testLaTeXWarningOn($lastline)) {
               $continueToCompile = $enableLoop;
       }
       close(*LOGFILE);
       # Output the detailled wanring message that was not already output
       if ($warning && $current_log_block) {
               if ($current_log_block =~ /^(.*?):([^:]*):([0-9]+):\s*(.*?)\s*$/) {
                       my ($filename, $extension, $line, $message) = ($1, $2, $3, $4);
                                               $self->_printWarning($filename, $extension, $line, $message);
               }
       }
       $self->{'warnings'}{'done'} = 1;
       return ($continueToCompile,$enableLoop);
}

sub _testLaTeXWarningOn($) : method {
       my $self = shift;
       my $line = shift;
       my $oline = "$line";
       $line =~ s/[\n\r\t \f]+//g;
       if ($line =~ /Warning.*re\-?run\s/i) {
               return 1;
       }
       elsif ($line =~ /Warning:Therewereundefinedreferences/i) {
               $self->{'warnings'}{'undefined_reference'} = 1;
       }
       elsif ($line =~ /Warning:Citation.+undefined/i) {
               $self->{'warnings'}{'undefined_citation'} = 1;
       }
       elsif ($line =~ /Warning:Thereweremultiply\-definedlabels/i) {
               $self->{'warnings'}{'multiple_definition'} = 1;
       }
       elsif ($line =~ /(?:\s|^)Warning/im) {
               $self->{'warnings'}{'other_warning'} = 1;
       }
       return 0;
}

=pod

=item * build($)

Build all the root files.

=over 4

=item B<progress> (optional) is the progress indicator to use.

=back

=cut
sub build(;$) : method {
       my $self = shift;
       my $progress = shift;

       my $progValue;
       if ($progress) {
               my $numberOfRootFiles = @{$self->{'rootFiles'}};
               $progress->setMax($numberOfRootFiles*100);
               $progValue = 0;
       }

       foreach my $rootFile (@{$self->{'rootFiles'}}) {

               my $sprogress = undef;
               if ($progress) {
                       $progress->setComment(formatText(_T("Generating {}"), basename($rootFile)));
                       $sprogress = $progress->subProgress(100);
                       $sprogress->setMax(1000);
               }

               # Read building stamps
               $self->_readBuildStamps($rootFile);

               $sprogress->setValue(10) if ($sprogress);

               # Launch at least one LaTeX compilation
               $self->runLaTeX($rootFile,0,1);

               $sprogress->setValue(210) if ($sprogress);

               # Compute the dependencies of the file
               $self->_computeDependenciesForRootFile($rootFile);

               $sprogress->setValue(260) if ($sprogress);

               # Construct the build list and launch the required builds
               my @builds = $self->_buildExecutionList("$rootFile");

               $sprogress->setValue(310) if ($sprogress);

               # Build the files
               if (@builds) {
                       my $sprogStep = 600 / @builds;
                       foreach my $file (@builds) {
                               if ($sprogress) {
                                       $sprogress->setComment(formatText(_T("Compiling {}"), basename($file)));
                               }
                               $self->_build($rootFile, $file);
                               $sprogress->increment($sprogStep) if ($sprogress);
                       }
               }

               # Output the warnings from the last TeX builds
               if ($self->{'buffered_warnings'}) {
                       foreach my $w (@{$self->{'buffered_warnings'}}) {
                               print STDERR "$w";
                       }
                       $self->{'buffered_warnings'} = [];
               }

               $sprogress->setValue(910) if ($sprogress);

               # Write building stamps
               $self->_writeBuildStamps($rootFile);

               # Generate the Postscript file when requested
               if (($self->{'configuration'}{'generation.generation type'}||'pdf') eq 'ps') {
                       my $dirname = dirname($rootFile);
                       my $basename = basename($rootFile, '.pdf', '.ps', '.dvi', '.xdv');
                       my $dviFile = File::Spec->catfile($dirname, $basename.'.dvi');
                       my $dviDate = lastFileChange("$dviFile");
                       if (defined($dviDate)) {
                               my $psFile = File::Spec->catfile($dirname, $basename.'.ps');
                               my $psDate = lastFileChange("$psFile");
                               if (!$psDate || ($dviDate>=$psDate)) {
                                       if ($sprogress) {
                                               $sprogress->setComment(formatText(_T("Generating {}"), basename($psFile)));
                                       }
                                       printDbg(formatText(_T('{}: {}'), 'DVI2PS', basename($dviFile)));
                                       runCommandOrFail(@{$self->{'dvi2ps_cmd'}},
                                               $self->makeRelativePath($dviFile));
                               }
                       }
               }

               if ($sprogress) {
                       $sprogress->setComment(formatText(_T("Analyzing logs for {}"), basename($rootFile)));
               }

               # Compute the log filename
               my $texFile = $self->{'files'}{$rootFile}{'mainFile'};
               my $logFile = File::Spec->catfile(dirname($texFile), basename($texFile, '.tex').'.log');

               # Detect warnings if not already done
               if (!%{$self->{'warnings'}}) {
                       $self->_testLaTeXWarningInFile($logFile, 0, 0);
               }

               # Output the last LaTeX warning indicators.
               if ($self->{'warning_level'}>0) {
                       if ($self->{'warnings'}{'multiple_definition'}) {
                               my $s = _T("LaTeX Warning: There were multiply-defined labels.\n");
                               if ($self->{'is_extended_warning_enable'}) {
                                       print STDERR "!!$logFile:W1: $s";
                               }
                               else {
                                       print STDERR "$s";
                               }
                       }
                       if ($self->{'warnings'}{'undefined_reference'}) {
                               my $s = _T("LaTeX Warning: There were undefined references.\n");
                               if ($self->{'is_extended_warning_enable'}) {
                                       print STDERR "!!$logFile:W2: $s";
                               }
                               else {
                                       print STDERR "$s";
                               }
                       }
                       if ($self->{'warnings'}{'undefined_citation'}) {
                               my $s = _T("LaTeX Warning: There were undefined citations.\n");
                               if ($self->{'is_extended_warning_enable'}) {
                                       print STDERR "!!$logFile:W3: $s";
                               }
                               else {
                                       print STDERR "$s";
                               }
                       }
                       if ($self->{'warnings'}{'other_warning'}) {
                               my $texFile = $rootFile;
                               if ($self->{'files'}{$rootFile}{'mainFile'}) {
                                       $texFile = $self->{'files'}{$rootFile}{'mainFile'};
                               }
                               print STDERR formatText(_T("LaTeX Warning: Please look inside {} for the other the warning messages.\n"),
                                               basename($logFile));
                       }
               }

               if ($progress) {
                       $progValue += 100;
                       $progress->setValue($progValue);
               }
       }

       $progress->stop() if ($progress);

       return undef;
}

=pod

=item * buildBiblio($)

Launch the Biblio only.

=over 4

=item B<progress> (optional) is the progress indicator to use.

=back

=cut
sub buildBiblio(;$) : method {
       my $self = shift;
       my $progress = shift;

       my $progValue;
       if ($progress) {
               my $numberOfRootFiles = @{$self->{'rootFiles'}};
               $progress->setMax($numberOfRootFiles*100);
               $progValue = 0;
       }

       foreach my $rootFile (@{$self->{'rootFiles'}}) {

               my $sprogress = undef;
               if ($progress) {
                       $sprogress = $progress->subProgress(100);
                       $sprogress->setMax(1000);
               }

               # Read building stamps
               $self->_readBuildStamps($rootFile);

               $sprogress->setValue(10) if ($sprogress);

               # Compute the dependencies of the file
               $self->_computeDependenciesForRootFile($rootFile);

               $sprogress->setValue(60) if ($sprogress);

               # Construct the build list and launch the required builds
               my @builds = $self->_buildExecutionList("$rootFile",1);

               $sprogress->setValue(110) if ($sprogress);

               if (@builds) {
                       foreach my $file (@builds) {
                               if (exists $self->{'files'}{$file}) {
                                       my $type = $self->{'files'}{$file}{'type'};
                                       if ($type eq 'bbl') {
                                               my $func = $self->can('__build_'.lc($type));
                                               if ($func) {
                                                       $func->($self, $rootFile, $file, $self->{'files'}{$file});
                                               }
                                       }
                               }
                       }
               }
               else {
                       printDbgFor(2, formatText(_T('{} is up-to-date.'), basename($rootFile)));
               }

               $sprogress->setValue(990) if ($sprogress);

               # Write building stamps
               $self->_writeBuildStamps($rootFile);

               if ($progress) {
                       $progValue += 100;
                       $progress->setValue($progValue);
               }
       }

       $progress->stop() if ($progress);

       return undef;
}

=pod

=item * buildMakeGlossaries($)

Launch the MakeGlossaries only.

=over 4

=item B<progress> (optional) is the progress indicator to use.

=back

=cut
sub buildMakeGlossaries(;$) : method {
       my $self = shift;
       my $progress = shift;

       my $progValue;
       if ($progress) {
               my $numberOfRootFiles = @{$self->{'rootFiles'}};
               $progress->setMax($numberOfRootFiles*100);
               $progValue = 0;
       }

       foreach my $rootFile (@{$self->{'rootFiles'}}) {

               my $sprogress = undef;
               if ($progress) {
                       $sprogress = $progress->subProgress(100);
                       $sprogress->setMax(1000);
               }

               # Read building stamps
               $self->_readBuildStamps($rootFile);

               $sprogress->setValue(10) if ($sprogress);

               # Compute the dependencies of the file
               $self->_computeDependenciesForRootFile($rootFile);

               $sprogress->setValue(60) if ($sprogress);

               # Construct the build list and launch the required builds
               my @builds = $self->_buildExecutionList("$rootFile",1);

               $sprogress->setValue(110) if ($sprogress);

               if (@builds) {
                       foreach my $file (@builds) {
                               if (exists $self->{'files'}{$file}) {
                                       my $type = $self->{'files'}{$file}{'type'};
                                       if ($type eq 'gls') {
                                               my $func = $self->can('__build_'.lc($type));
                                               if ($func) {
                                                       $func->($self, $rootFile, $file, $self->{'files'}{$file});
                                                       return undef;
                                               }
                                       }
                               }
                       }
               }
               else {
                       printDbgFor(2, formatText(_T('{} is up-to-date.'), basename($rootFile)));
               }

               $sprogress->setValue(990) if ($sprogress);

               # Write building stamps
               $self->_writeBuildStamps($rootFile);

               if ($progress) {
                       $progValue += 100;
                       $progress->setValue($progValue);
               }
       }

       $progress->stop() if ($progress);

       return undef;
}

=pod

=item * buildMakeIndex($)

Launch the MakeIndex only.

=over 4

=item B<progress> (optional) is the progress indicator to use.

=back

=cut
sub buildMakeIndex(;$) : method {
       my $self = shift;
       my $progress = shift;

       my $progValue;
       if ($progress) {
               my $numberOfRootFiles = @{$self->{'rootFiles'}};
               $progress->setMax($numberOfRootFiles*100);
               $progValue = 0;
       }

       foreach my $rootFile (@{$self->{'rootFiles'}}) {

               my $sprogress = undef;
               if ($progress) {
                       $sprogress = $progress->subProgress(100);
                       $sprogress->setMax(1000);
               }

               # Read building stamps
               $self->_readBuildStamps($rootFile);

               $sprogress->setValue(10) if ($sprogress);

               # Compute the dependencies of the file
               $self->_computeDependenciesForRootFile($rootFile);

               $sprogress->setValue(60) if ($sprogress);

               # Construct the build list and launch the required builds
               my @builds = $self->_buildExecutionList("$rootFile",1);

               $sprogress->setValue(110) if ($sprogress);

               if (@builds) {
                       foreach my $file (@builds) {
                               if (exists $self->{'files'}{$file}) {
                                       my $type = $self->{'files'}{$file}{'type'};
                                       if ($type eq 'ind') {
                                               my $func = $self->can('__build_'.lc($type));
                                               if ($func) {
                                                       $func->($self, $rootFile, $file, $self->{'files'}{$file});
                                                       return undef;
                                               }
                                       }
                               }
                       }
               }
               else {
                       printDbgFor(2, formatText(_T('{} is up-to-date.'), basename($rootFile)));
               }

               $sprogress->setValue(990) if ($sprogress);

               # Write building stamps
               $self->_writeBuildStamps($rootFile);

               if ($progress) {
                       $progValue += 100;
                       $progress->setValue($progValue);
               }
       }

       $progress->stop() if ($progress);

       return undef;
}

# Read the building stamps.
# This function puts the stamps in $self->{'stamps'}.
# Parameter:
# $_[0] = path to the root TeX file.
# Result: nothing
sub _readBuildStamps($) : method {
       my $self = shift;
       my $rootFile = shift;
       my $stampFile = File::Spec->catfile(dirname($rootFile), '.autolatex_stamp');
       if (exists $self->{'stamps'}) {
               delete $self->{'stamps'};
       }
       if (-r "$stampFile") {
               local *FILE;
               open(*FILE, "< $stampFile") or printErr("$stampFile: $!");
               while (my $line = <FILE>) {
                       if ($line =~ /^BIB\(([^)]+?)\)\:(.+)$/) {
                               my ($k,$n) = ($1,$2);
                               $self->{'stamps'}{'bib'}{$n} = $k;
                       }
                       if ($line =~ /^IDX\(([^)]+?)\)\:(.+)$/) {
                               my ($k,$n) = ($1,$2);
                               $self->{'stamps'}{'idx'}{$n} = $k;
                       }
                       if ($line =~ /^GLS\(([^)]+?)\)\:(.+)$/) {
                               my ($k,$n) = ($1,$2);
                               $self->{'stamps'}{'gls'}{$n} = $k;
                       }
               }
               close(*FILE);
       }
}

# Write the building stamps.
# This function gets the stamps from $self->{'stamps'}.
# Parameter:
# $_[0] = path to the root TeX file.
# Result: nothing
sub _writeBuildStamps($) : method {
       my $self = shift;
       my $rootFile = shift;
       my $stampFile = File::Spec->catfile(dirname($rootFile), '.autolatex_stamp');
       local *FILE;
       open(*FILE, "> $stampFile") or printErr("$stampFile: $!");
       if ($self->{'stamps'}{'bib'}) {
               while (my ($k,$v) = each(%{$self->{'stamps'}{'bib'}})) {
                       print FILE "BIB($v):$k\n";
               }
       }
       if ($self->{'stamps'}{'idx'}) {
               while (my ($k,$v) = each(%{$self->{'stamps'}{'idx'}})) {
                       print FILE "IDX($v):$k\n";
               }
       }
       if ($self->{'stamps'}{'gls'}) {
               while (my ($k,$v) = each(%{$self->{'stamps'}{'gls'}})) {
                       print FILE "GLS($v):$k\n";
               }
       }
       close(*FILE);
}

# Static function that is testing if the timestamp a is
# more recent than the timestamp b.
# Parameters:
# $_[0] = a.
# $_[1] = b.
# Result: true if a is more recent than b, or not defined;
#         false otherwise.
sub _a_more_recent_than_b($$) {
       my $a = shift;
       my $b = shift;
       return (!defined($a) || (defined($b) && $a>$b));
}

# Test if the specified file is needing to be rebuild.
# Parameters:
# $_[0] = timestamp of the root file.
# $_[1] = filename of the file to test.
# $_[2] = parent element of the file, of type Entry.
# $_[3] = is the description of the file to test.
# Result: true if the file is needing to be rebuild,
#         false if the file is up-to-date.
sub _need_rebuild($$$$) : method {
       my $self = shift;
       my $rootchange = shift;
       my $filename = shift;
       my $parent = shift;
       my $file = shift;
       if (!defined($file->{'change'}) || (!-f "$filename")) {
               return 1;
       }

       if ($filename =~ /(\.[^.]+)$/) {
               my $ext = $1;
               if ($ext eq '.bbl') {
                       if ($file->{'use_biber'}) {
                               # Parse the BCF file to detect the citations
                               my $bcfFile = File::Spec->catfile(dirname($filename), basename($filename, '.bbl').'.bcf');
                               my $currentMd5 = makeBcfBibliographyCitationMd5($bcfFile) || '';
                               my $oldMd5 = $self->{'stamps'}{'bib'}{$bcfFile} || '';
                               if ($currentMd5 ne $oldMd5) {
                                       $self->{'stamps'}{'bib'}{$bcfFile} = $currentMd5;
                                       return 1;
                               }
                       }
                       else {
                               # Parse the AUX file to detect the citations
                               my $auxFile = File::Spec->catfile(dirname($filename), basename($filename, '.bbl').'.aux');
                               my $currentMd5 = makeAuxBibliographyCitationMd5($auxFile) || '';
                               my $oldMd5 = $self->{'stamps'}{'bib'}{$auxFile} || '';
                               if ($currentMd5 ne $oldMd5) {
                                       $self->{'stamps'}{'bib'}{$auxFile} = $currentMd5;
                                       return 1;
                               }
                       }
                       return 0;
               }
               elsif ($ext eq '.ind') {
                       # Parse the IDX file to detect the index definitions
                       my $idxFile = File::Spec->catfile(dirname($filename), basename($filename, '.ind').'.idx');
                       my $currentMd5 = makeIdxIndexDefinitionMd5($idxFile) || '';
                       my $oldMd5 = $self->{'stamps'}{'idx'}{$idxFile} || '';
                       if ($currentMd5 ne $oldMd5) {
                               $self->{'stamps'}{'idx'}{$idxFile} = $currentMd5;
                               return 1;
                       }
                       return 0;
               }
               elsif ($ext eq '.gls') {
                       # Parse the GLS file to detect the index definitions
                       my $glsFile = File::Spec->catfile(dirname($filename), basename($filename, '.pdf').'.gls');
                       my $currentMd5 = makeGlsIndexDefinitionMd5($glsFile) || '';
                       my $oldMd5 = $self->{'stamps'}{'gls'}{$glsFile} || '';
                       if ($currentMd5 ne $oldMd5) {
                               $self->{'stamps'}{'gls'}{$glsFile} = $currentMd5;
                               return 1;
                       }
                       return 0;
               }
       }

       return _a_more_recent_than_b( $file->{'change'}, $rootchange );
}

# Build the list of the files to be build.
# Parameters:
# $_[0] = name of the root file that should be build.
# $_[1] = boolean value that permits to force to consider all the files has changed.
# Result: the build list.
sub _buildExecutionList($;$) : method {
       my $self = shift;
       my $rootfile = shift;
       my $forceChange = shift;
       my @builds = ();

       # Go through the dependency tree with en iterative algorithm

       my $rootchange = $self->{'files'}{$rootfile}{'change'};
       my $element = newEntry($rootfile,undef) ;
       my $child;
       my @iterator = ( $element );
       while (@iterator) {
               $element = pop @iterator;
               my $deps = $self->{'files'}{$element->file}{'dependencies'};
               if ($element->go_up || !%$deps) {
                       if (    $forceChange ||
                               $element->rebuild ||
                               $self->_need_rebuild(
                                       $rootchange,
                                       $element->file,
                                       $element->parent,
                                       $self->{'files'}{$element->file})) {

                               if ($element->parent) {
                                       $element->parent->rebuild(1);
                               }

                               if ($self->can('__build_'.lc($self->{'files'}{$element->file}{'type'}))) {
                                       push @builds, $element->file;
                               }

                       }
               }
               else {
                       push @iterator, $element;
                       foreach my $dep (keys %$deps) {
                               $child = newEntry($dep,$element);
                               push @iterator, $child;
                       }
                       $element->go_up(1);
               }
       }
       return @builds;
}

# Run the building process.
# Parameters:
# $_[0] = name of the root file that should be build.
# $_[1] = name of the file to build (the root file or one of its dependencies).
# Result: nothing.
sub _build($$) : method {
       my $self = shift;
       my $rootFile = shift;
       my $file = shift;

       if (exists $self->{'files'}{$file}) {
               my $type = $self->{'files'}{$file}{'type'};
               if ($type) {
                       my $func = $self->can('__build_'.lc($type));
                       if ($func) {
                               $func->($self, $rootFile, $file, $self->{'files'}{$file});
                               return undef;
                       }
               }
       }

       # Default building behavior: do nothing
       return undef;
}

sub __find_file_with_basename($) {
       my $self = shift;
       my $basename = shift;
       if (%{$self->{'files'}}) {
               foreach my $k (keys %{$self->{'files'}}) {
                       my $bn = basename($k);
                       if ($bn eq $basename) {
                               return File::Spec->abs2rel($k,
                                       $self->{'configuration'}{'__private__'}{'input.project directory'});
                       }
               }
       }
       return $basename;
}

# Callback to build a BBL file.
# Parameters:
# $_[0] = name of the root file that should be build.
# $_[1] = name of the file to build (the root file or one of its dependencies).
# $_[2] = description of the file to build.
# Result: nothing.
sub __build_bbl($$$) : method {
       my $self = shift;
       my $rootFile = shift;
       my $file = shift;
       my $filedesc = shift;
       if ($self->{'is_biblio_enable'}) {
               my $basename = basename($file,'.bbl');
               if ($filedesc->{'use_biber'}) {
                       ####################################
                       # BIBER
                       ####################################
                       printDbg(formatText(_T('{}: {}'), 'BIBER', basename($basename)));
                       my $retcode = runCommandRedirectToInternalLogs(
                                       @{$self->{'biber_cmd'}}, "$basename");
                       # Output the log from the bibliography tool
                       if ($retcode!=0) {
                               printDbg(formatText(_T("{}: Error when processing {}"), 'BIBER', $basename));
                               local *INFILE;
                               open(*INFILE, "<autolatex_exec_stdout.log") or printErr("autolatex_exec_stdout.log: $!");
                               while (my $line = <INFILE>) {
                                       if ($line =~ /^\s*ERROR\s*\-\s*.*subsystem:\s*(.+?),\s*line\s+([0-9]+),\s*(.*?)\s*$/i) {
                                               my ($filename, $linenumber, $message) = ($1, $2, $3);
                                               if ($filename =~ /^(.+)_[0-9]+\.[a-zA-Z0-9_-]+$/) {
                                                       $filename = $1;
                                               }
                                               $filename = $self->__find_file_with_basename(basename($filename));
                                               print STDERR "$filename:$linenumber: $message\n";
                                       }
                                       elsif ($line =~ /^\s*ERROR\s*\-\s*(.+?)\s*$/i) {
                                               my $message = $1;
                                               print STDERR "$message\n";
                                       }
                               }
                               close(*INFILE);
                               exit(255);
                       }
                       else {
                               unlink("autolatex_exec_stdout.log");
                               unlink("autolatex_exec_stderr.log");
                       }
               }
               else {
                       ####################################
                       # BIBTEX
                       ####################################
                       my $auxFile = File::Spec->catfile(dirname($file),"$basename.aux");
                       printDbg(formatText(_T('{}: {}'), 'BIBTEX', basename($auxFile)));
                       my $retcode = runCommandRedirectToInternalLogs(
                                       @{$self->{'bibtex_cmd'}},
                                               $self->makeRelativePath("$auxFile"));

                       # Output the log from the bibliography tool
                       if ($retcode!=0) {

                               printDbg(formatText(_T("{}: Error when processing {}"), 'BIBTEX', basename($auxFile)));
                               local *INFILE;
                               open(*INFILE, "<autolatex_exec_stdout.log") or printErr("autolatex_exec_stdout.log: $!");
                               my %currentError = ();
                               my $previousline = '';
                               while (my $line = <INFILE>) {
                                       if (%currentError) {
                                               if ($line =~ /^\s*:\s*(.*?)\s*$/) {
                                                       $currentError{'message'} .= " $1";
                                               }
                                               else {
                                                       print STDERR $currentError{'filename'}.':'.$currentError{'lineno'}.': '.$currentError{'message'}."\n";
                                                       %currentError = ();
                                               }
                                       }
                                       elsif ($line =~ /^\s*(.*?)\s*\-\-\-line\s+([0-9]+)\s+of\s+file\s+(.*?)\s*$/i) {
                                               my ($message, $linenumber, $filename) = ($1, $2, $3);
                                               if (!$message) {
                                                       $message = $previousline;
                                                       $message =~ s/^\s+//s;
                                                       $message =~ s/\s+$//s;
                                               }
                                               %currentError = (
                                                       'filename' => $filename,
                                                       'lineno' => $linenumber,
                                                       'message' => $message,
                                               );
                                               $previousline = '';
                                       }
                                       else {
                                               $previousline = $line;
                                               %currentError = ();
                                       }
                               }
                               close(*INFILE);
                               if (%currentError) {
                                       print STDERR $currentError{'filename'}.':'.
                                               $currentError{'lineno'}.': '.$currentError{'message'}."\n";
                               }
                               exit(255);
                       }
                       else {
                               unlink("autolatex_exec_stdout.log");
                               unlink("autolatex_exec_stderr.log");
                       }
               }
       }
}

# Callback to build a IND file.
# Parameters:
# $_[0] = name of the root file that should be build.
# $_[1] = name of the file to build (the root file or one of its dependencies).
# $_[2] = description of the file to build.
# Result: nothing.
sub __build_ind($$$) : method {
       my $self = shift;
       my $rootFile = shift;
       my $file = shift;
       my $filedesc = shift;
       if ($self->{'is_makeindex_enable'}) {
               my $basename = basename($file,'.ind');
               my $idxFile = File::Spec->catfile(dirname($file),"$basename.idx");
               if (-f "$idxFile") {
                       printDbg(formatText(_T('{}: {}'), 'MAKEINDEX', basename($idxFile)));
                       my @styleArgs = ();
                       my $istFile = $self->{'configuration'}{'__private__'}{'output.ist file'};
                       if ($istFile && -f "$istFile") {
                               printDbgFor(2, formatText(_T('Style file: {}'), $istFile));
                               push @styleArgs, '-s', "$istFile";
                       }
                       runCommandOrFail(@{$self->{'makeindex_cmd'}}, @styleArgs,
                               $self->makeRelativePath("$idxFile"));
               }
       }
}

# Callback to build a GLS file.
# Parameters:
# $_[0] = name of the root file that should be build.
# $_[1] = name of the file to build (the root file or one of its dependencies).
# $_[2] = description of the file to build.
# Result: nothing.
sub __build_gls($$$) : method {
       my $self = shift;
       my $rootFile = shift;
       my $file = shift;
       my $filedesc = shift;
       if ($self->{'is_makeglossaries_enable'}) {
               my $filename = File::Spec->catfile(dirname($rootFile), basename($rootFile,'.pdf'));
               $filename = $self->makeRelativePath("$filename");
               printDbg(formatText(_T('{}: {}'), 'MAKEGLOSSARIES', basename($rootFile)));
               runCommandOrFail(@{$self->{'makeglossaries_cmd'}}, "$filename");
       }
}

# Callback to build a PDF file.
# Parameters:
# $_[0] = name of the root file that should be build.
# $_[1] = name of the file to build (the root file or one of its dependencies).
# $_[2] = description of the file to build.
# Result: nothing.
sub __build_pdf($$$) : method {
       my $self = shift;
       my $rootFile = shift;
       my $file = shift;
       my $filedesc = shift;
       my $runs = 2;
       my $majorFailure = 0;
       do {
               $runs--;
               $self->runLaTeX($file,1,1);
               $majorFailure = (exists $self->{'warnings'}{'multiple_definition'}) ||
                               (exists $self->{'warnings'}{'undefined_reference'}) ||
                               (exists $self->{'warnings'}{'undefined_citation'});
       }
       while ($majorFailure && $runs>0);
}

=pod

=item * enableBiblio

Enable or disable the call to bibtex/biber.
If this function has a parameter, the flag is changed.

=over

=item * isEnable (optional boolean)

=back

I<Returns:> the value of the enabling flag.

=cut
sub enableBiblio : method {
       my $self = shift;
       if (@_) {
               $self->{'is_biblio_enable'} = $_[0];
       }
       return $self->{'is_biblio_enable'};
}

=pod

=item * enableMakeIndex

Enable or disable the call to makeindex.
If this function has a parameter, the flag is changed.

=over

=item * isEnable (optional boolean)

=back

I<Returns:> the vlaue of the enabling flag.

=cut
sub enableMakeIndex : method {
       my $self = shift;
       if (@_) {
               $self->{'is_makeindex_enable'} = $_[0];
       }
       return $self->{'is_makeindex_enable'};
}

=pod

=item * enableMakeGlossaries

Enable or disable the call to makeglossaries.
If this function has a parameter, the flag is changed.

=over

=item * isEnable (optional boolean)

=back

I<Returns:> the vlaue of the enabling flag.

=cut
sub enableMakeGlossaries : method {
       my $self = shift;
       if (@_) {
               $self->{'is_makeglossaries_enable'} = $_[0];
       }
       return $self->{'is_makeglossaries_enable'};
}

=pod

=item * generationType

Get or change the type of generation.
If this function has a parameter, the type is changed.

=over

=item * type (optional string)

C<"pdf"> to use pdflatex, C<"dvi"> to use latex, C<"ps"> to use latex and dvips, C<"pspdf"> to use latex, dvips and ps2pdf.

=back

I<Returns:> the generation type.

=cut
sub generationType : method {
       my $self = shift;
       if (@_) {
               my $type = $_[0] || 'pdf';
               if ($type ne 'dvi' && $type ne 'ps' && $type ne 'pdf') {
                       $type = 'pdf';
               }
               $self->{'generation_type'} = $type;
       }
       return $self->{'generation_type'};
}

1;
__END__
=back

=head1 BUG REPORT AND FEEDBACK

To report bug, provide feedback, suggest new features, etc. visit the AutoLaTeX Project management page at <http://www.arakhne.org/autolatex/> or send email to the author at L<[email protected]>.

=head1 LICENSE

S<GNU Public License (GPL)>

=head1 COPYRIGHT

S<Copyright (c) 2013-2016 Stéphane Galland E<lt>[email protected]<gt>>

=head1 SEE ALSO

L<autolatex-dev>