# autolatex - TeXDependencyAnalyzer.pm
# Copyright (C) 2013-17  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

TeXDependencyAnalyzer.pm - Detect dependencies of a TeX file.

=head1 DESCRIPTION

Tool that is extracting the dependencies of the TeX file.

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

=head1 FUNCTIONS

The provided functions are:

=over 4

=cut
package AutoLaTeX::TeX::TeXDependencyAnalyzer;

$VERSION = '7.0';
@ISA = ('Exporter');
@EXPORT = qw( &getDependenciesOfTeX ) ;
@EXPORT_OK = qw();

require 5.014;
use strict;
use utf8;
use vars qw(@ISA @EXPORT @EXPORT_OK $VERSION);
use Config; # Perl configuration
use File::Spec;
use File::Basename;
use AutoLaTeX::Core::Util;
use AutoLaTeX::TeX::TeXParser;

my %MACROS = (
       'input'                 => '!{}',
       'include'               => '!{}',
       'makeindex'             => '[]',
       'printindex'            => '[]',
       'makeglossaries'        => '',
       'printglossaries'       => '',
       'printglossary'         => '![]',
       'usepackage'            => '![]!{}',
       'RequirePackage'        => '![]!{}',
       'documentclass'         => '![]!{}',
       'addbibresource'        => '![]!{}',
       );

=pod

=item B<getDependenciesOfTeX($)>

Parse a TeX file and detect the included files.

=over 4

=item * C<file> is the name of the TeX file to parse.

=item * C<dir> is the reference directory for the relative path.

=back

I<Returns:> the included files from the TeX file into an associative array.

=cut
sub getDependenciesOfTeX($$) {
       my $input = shift;
       my $rootdir = shift;

       local *FILE;
       open(*FILE, "< $input") or printErr("$input: $!");
       my $content = '';
       while (my $line = <FILE>) {
               $content .= $line;
       }
       close(*FILE);

       my $listener = AutoLaTeX::TeX::TeXDependencyAnalyzer->_new($input,$rootdir);

       my $parser = AutoLaTeX::TeX::TeXParser->new("$input", $listener);

       while (my ($k,$v) = each(%MACROS)) {
               $parser->addTextModeMacro($k,$v);
               $parser->addMathModeMacro($k,$v);
       }

       $parser->parse( $content );

       my %analysis = ( %{$listener->{'dependencies'}} );

       foreach my $cat ('sty', 'tex') {
               if ($analysis{$cat}) {
                       my @t = keys %{$analysis{$cat}};
                       $analysis{$cat} = \@t;
               }
       }

       while (my ($bibdb,$bibdt) = each(%{$analysis{'biblio'}})) {
               foreach my $cat ('bib', 'bst', 'bbx', 'cbx') {
                       if ($bibdt->{$cat}) {
                               my @t = keys %{$bibdt->{$cat}};
                               $bibdt->{$cat} = \@t;
                       }
               }
       }

       $analysis{'biber'} = $listener->{'is_biber'};

       return %analysis;
}

sub _expandMacro($$@) : method {
       my $self = shift;
       my $parser = shift;
       my $macro = shift;

       if ( $macro eq '\\include' || $macro eq '\\input' ) {
               foreach my $param (@_) {
                       my $value = $param->{'text'};
                       if ($value) {
                               my $texFile = "$value.tex";
                               if (!File::Spec->file_name_is_absolute($texFile)) {
                                       $texFile = File::Spec->catfile($self->{'dirname'}, "$texFile");
                               }
                               if (-f "$texFile") {
                                       $self->{'dependencies'}{'tex'}{$texFile} = 1;
                               }
                       }
               }
       }
       elsif ( $macro eq '\\makeindex' || $macro eq '\\printindex' ) {
               $self->_addidxreference(@_);
       }
       elsif ( $macro eq '\\makeglossaries' || $macro eq '\\printglossaries' || $macro eq '\\printglossary' ) {
               $self->{'dependencies'}{'glo'} = 1;
       }
       elsif ( $macro eq '\\usepackage' || $macro eq '\\RequirePackage' ) {
               my $sty = $_[1]{'text'};
               my $styFile = "$sty";
               if ($styFile !~ /\.sty$/i) {
                       $styFile .= ".sty";
               }
               if ($styFile eq 'multibib.sty') {
                       $self->{'is_multibib'} = 1;
               }
               if ($styFile eq 'biblatex.sty') {
                       $self->{'is_biblatex'} = 1;
                       # Parse the biblatex parameters
                       if ($_[0] && $_[0]->{'text'}) {
                               my @params = split(/\s*\,\s*/, trim($_[0]->{'text'} || ''));
                               foreach my $p (@params) {
                                       my ($k, $v);
                                       if ($p =~ /^([^=]+)\s*=\s*(.*?)\s*$/) {
                                               $k = $1;
                                               $v = $2 || '';
                                       }
                                       else {
                                               $k = $p;
                                               $v = '';
                                       }
                                       if  ($k eq 'backend') {
                                               $self->{'is_biber'} = ($v eq 'biber');
                                       }
                                       elsif  ($k eq 'style') {
                                               my $bbxFile = "$v";
                                               if ($bbxFile !~ /\.bbx$/i) {
                                                       $bbxFile .= ".bbx";
                                               }
                                               if (!File::Spec->file_name_is_absolute($bbxFile)) {
                                                       $bbxFile = File::Spec->catfile($self->{'dirname'}, "$bbxFile");
                                               }
                                               if (-f "$bbxFile") {
                                                       $self->{'dependencies'}{'biblio'}{''}{'bbx'}{$bbxFile} = 1;
                                               }
                                               my $cbxFile = "$v";
                                               if ($cbxFile !~ /\.cbx$/i) {
                                                       $cbxFile .= ".cbx";
                                               }
                                               if (!File::Spec->file_name_is_absolute($cbxFile)) {
                                                       $cbxFile = File::Spec->catfile($self->{'dirname'}, "$cbxFile");
                                               }
                                               if (-f "$cbxFile") {
                                                       $self->{'dependencies'}{'biblio'}{''}{'cbx'}{$cbxFile} = 1;
                                               }
                                       }
                                       elsif ($k eq 'bibstyle') {
                                               my $bbxFile = "$v";
                                               if ($bbxFile !~ /\.bbx$/i) {
                                                       $bbxFile .= ".bbx";
                                               }
                                               if (!File::Spec->file_name_is_absolute($bbxFile)) {
                                                       $bbxFile = File::Spec->catfile($self->{'dirname'}, "$bbxFile");
                                               }
                                               if (-f "$bbxFile") {
                                                       $self->{'dependencies'}{'biblio'}{''}{'bbx'}{$bbxFile} = 1;
                                               }
                                       }
                                       elsif ($k eq 'citestyle') {
                                               my $cbxFile = "$v";
                                               if ($cbxFile !~ /\.cbx$/i) {
                                                       $cbxFile .= ".cbx";
                                               }
                                               if (!File::Spec->file_name_is_absolute($cbxFile)) {
                                                       $cbxFile = File::Spec->catfile($self->{'dirname'}, "$cbxFile");
                                               }
                                               if (-f "$cbxFile") {
                                                       $self->{'dependencies'}{'biblio'}{''}{'cbx'}{$cbxFile} = 1;
                                               }
                                       }
                               }
                       }
               }
               if (!File::Spec->file_name_is_absolute($styFile)) {
                       $styFile = File::Spec->catfile($self->{'dirname'}, "$styFile");
               }
               if (-f "$styFile") {
                       $self->{'dependencies'}{'sty'}{"$styFile"} = 1;
               }
       }
       elsif ($macro eq '\\documentclass' ) {
               my $cls = $_[1]{'text'};
               my $clsFile = "$cls";
               if ($clsFile !~ /\.cls$/i) {
                       $clsFile .= ".cls";
               }
               if (!File::Spec->file_name_is_absolute($clsFile)) {
                       $clsFile = File::Spec->catfile($self->{'dirname'}, "$clsFile");
               }
               if (-f "$clsFile") {
                       $self->{'dependencies'}{'cls'} = [ "$clsFile" ];
               }
       }
       elsif ($macro =~ /^\\bibliographystyle(.*)$/s ) {
               my $bibdb = $1;
               $bibdb = $self->{'basename'} unless ($bibdb && $self->{'is_multibib'});
               foreach my $param (@_) {
                       my $value = $param->{'text'};
                       if ($value) {
                               foreach my $svalue (split(/\s*,\s*/,trim($value))) {
                                       if ($svalue) {
                                               my $bstFile = "$svalue";
                                               if ($bstFile !~ /\.bst$/i) {
                                                       $bstFile .= ".bst";
                                               }
                                               if (!File::Spec->file_name_is_absolute($bstFile)) {
                                                       $bstFile = File::Spec->catfile($self->{'dirname'}, "$bstFile");
                                               }
                                               if (-f "$bstFile") {
                                                       $self->{'dependencies'}{'biblio'}{$bibdb}{'bst'}{$bstFile} = 1;
                                               }
                                       }
                               }
                       }
               }
       }
       elsif ($macro =~ /^\\bibliography(.*)$/s) {
               my $bibdb = $1;
               $bibdb = $self->{'basename'} unless ($bibdb && $self->{'is_multibib'});
               $self->_addbibreference($bibdb,@_);
       }
       elsif ($macro eq '\\addbibresource') {
               my $bibdb = $self->{'basename'};
               $self->_addbibreference($bibdb,@_);
       }
       return '';
}

sub _addidxreference($@) {
       my $self = shift;
       for my $param (@_) {
               my $text = $param->{'text'} || '';
               if ($text =~ /name=([^,]+)/) {
                       my $name = "$1";
                       if (!$self->{'dependencies'}{'idx'}) {
                               $self->{'dependencies'}{'idx'} = [];
                       }
                       push @{$self->{'dependencies'}{'idx'}}, $name;
               }
       }
       if (!$self->{'dependencies'}{'idx'}) {
               $self->{'dependencies'}{'idx'} = [];
       }
       push @{$self->{'dependencies'}{'idx'}}, "";
}

sub _addbibreference($@) {
       my $self = shift;
       my $bibdb = shift || '';
       foreach my $param (@_) {
               my $value = $param->{'text'};
               if ($value) {
                       foreach my $svalue (split(/\s*,\s*/, $value)) {
                               if ($svalue) {
                                       my $bibFile = "$svalue";
                                       if ($bibFile !~ /\.bib$/i) {
                                               $bibFile .= ".bib";
                                       }
                                       if (!File::Spec->file_name_is_absolute($bibFile)) {
                                               $bibFile = File::Spec->catfile($self->{'dirname'}, "$bibFile");
                                       }
                                       if (-f "$bibFile") {
                                               $self->{'dependencies'}{'biblio'}{$bibdb}{'bib'}{$bibFile} = 1;
                                       }
                               }
                       }
               }
       }
}

sub _discoverMacroDefinition($$$$) : method {
       my $self = shift;
       my $parser = shift;
       my $macro = shift;
       my $special = shift;
       if (!$special) {
               if ($macro =~ /^bibliographystyle/s ) {
                       return '!{}';
               }
               elsif ($macro =~ /^bibliography/s ) {
                       return '!{}';
               }
       }
       return undef;
}

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

       my $self ;
       if ( $parent ) {
               %{$self} = %{$parent} ;
       }
       else {
               $self = {
                       'basename' => basename($_[0],'.tex'),
                       'file' => $_[0],
                       'dirname' => File::Spec->rel2abs($_[1]),
                       'expandMacro' => \&_expandMacro,
                       'discoverMacroDefinition' => \&_discoverMacroDefinition,
                       'dependencies' => {
                               'biblio' => {},
                               'tex' => {},
                               'sty' => {},
                               'cls' => [],
                               'idx' => [],
                       },
               };
       }
       bless( $self, $class );
       return $self;
}

1;
__END__
=back

=head1 BUG REPORT AND FEEDBACK

To report bugs, 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 Stéphane Galland E<lt>[email protected]<gt>>

=head1 SEE ALSO

L<autolatex-dev>