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

Flattener.pm - Make a TeX document flatten

=head1 DESCRIPTION

This tool creates a flattened version of a TeX document.
A flattened document contains a single TeX file, and all the
other related files are put inside the same directory of
the TeX file.

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

=head1 FUNCTIONS

The provided functions are:

=over 4

=cut
package AutoLaTeX::TeX::Flattener;

$VERSION = '7.0';
@ISA = ('Exporter');
@EXPORT = qw( &flattenTeX ) ;
@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 File::Copy;
use File::Path qw(make_path remove_tree);

use AutoLaTeX::Core::Util;
use AutoLaTeX::Core::IntUtils;
use AutoLaTeX::TeX::TeXParser;

my %MACROS = (
       'input'                         => '!{}',
       'include'                       => '!{}',
       'usepackage'                    => '![]!{}',
       'RequirePackage'                => '![]!{}',
       'documentclass'                 => '![]!{}',
       'includeanimatedfigure'         => '![]!{}',
       'includeanimatedfigurewtex'     => '![]!{}',
       'includefigurewtex'             => '![]!{}',
       'includegraphics'               => '![]!{}',
       'includegraphicswtex'           => '![]!{}',
       'graphicspath'                  => '![]!{}',
       'mfigure'                       => '![]!{}!{}!{}!{}',
       'mfigure*'                      => '![]!{}!{}!{}!{}',
       'msubfigure'                    => '![]!{}!{}!{}',
       'msubfigure*'                   => '![]!{}!{}!{}',
       'mfiguretex'                    => '![]!{}!{}!{}!{}',
       'mfiguretex*'                   => '![]!{}!{}!{}!{}',
       'addbibresource'                => '![]!{}',
       );

=pod

=item B<flattenTeX($$\@$)>

This functions creates a flattened version of a TeX document.
A flattened document contains a single TeX file, and all the
other related files are put inside the same directory of
the TeX file.

=over 4

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

=item * C<outputDir> is the name of the output directory.

=item * C<images> is the array that lists the images of the document.

=item * C<usebiblio> indicates if the flattener should use Bibliography instead of inline bibliography.

=back

=cut
sub flattenTeX($$\@$) {
       my $input = shift;
       my $output = shift;
       my $imageDb = shift;
       my $usebiblio = shift;
       return '' unless ($output);

       if (-d "$output") {
               remove_tree("$output");
       }

       make_path("$output") or printErr(formatText(_T("{}: {}"), $output, $!));

       printDbg(formatText(_T('Analysing {}'), basename($input)));
       my $content = readFileLines("$input");

       my $listener = AutoLaTeX::TeX::Flattener->_new($input, $output, $imageDb, $usebiblio);

       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 );

       # Replace PREAMBLE content
       if ($listener->{'data'}{'expandedContent'}) {
               my $preamble = '';
               foreach my $entry (values %{$listener->{'preamble'}}) {
                       if ($preamble) {
                               $preamble .= "\n";
                       }
                       $preamble .= $entry;
               }
               $listener->{'data'}{'expandedContent'} =~ s/\Q%%%%% AUTOLATEX PREAMBLE\E/$preamble/;
       }

       # Create the main TeX file
       my $outputFile = File::Spec->catfile($output, basename($input));
       printDbg(formatText(_T('Writing {}'), basename($outputFile)));
       writeFileLines($outputFile, $listener->{'data'}{'expandedContent'});

       # Make the copy of the resources
       foreach my $cat ('bib', 'cls', 'bst', 'sty', 'figures') {
               while (my ($source, $target) = each(%{$listener->{'data'}{$cat}})) {
                       $target = File::Spec->catfile("$output", "$target");
                       printDbg(formatText(_T('Copying resource {} to {}'), basename($source), basename($target)));
                       copy("$source", "$target") or printErr(formatText(_T("{} -> {}: {}"), $source, $target, $!));
               }
       }
}

sub _makeFilename($$;@) {
       my $self = shift;
       my $fn = shift || '';
       my $ext = shift || '';
       my $changed;
       do {
               $changed = 0;
               foreach my $e (@_) {
                       if ($fn =~ /^(.+)\Q$e\E$/i) {
                               $fn = $1;
                               $changed = 1;
                       }
               }
       }
       while ($changed);
       if ($ext && $fn !~ /\Q$ext\E$/i) {
               $fn .= $ext;
       }
       if (!File::Spec->file_name_is_absolute($fn)) {
               return File::Spec->catfile($self->{'dirname'}, $fn);
       }
       return $fn;
}

sub _isDocumentFile($) {
       my $self = shift;
       my $filename = shift;
       if (-f "$filename") {
               my $root = $self->{'dirname'};
               return "$filename" =~ /^\Q$root\E/s;
       }
       return 0;
}

sub _isDocumentPicture($) {
       my $self = shift;
       my $filename = shift;
       return 0;
}

sub _uniq($$) {
       my $self = shift;
       my $filename = shift;
       my $ext = shift;
       my $bn = basename($filename, $ext);
       my $name = $bn;
       my $i = 0;
       while (exists $self->{'data'}{'uniq'}{"$name$ext"}) {
               $name = "${bn}_$i";
               $i++;
       }
       $self->{'data'}{'uniq'}{"$name$ext"} = $filename;
       return $name;
}

sub _findPicture($) {
       my $self = shift;
       my $texname = shift;
       my $prefix = '';
       my $filename = $self->_makeFilename($texname,'');
       my @texexts = ('.pdftex_t','.pstex_t','.pdf_tex','.ps_tex','.tex');
       if (!-f $filename) {
               my @figexts = ( '.pdf', '.eps', '.ps',
                               '.png', '.jpeg', '.jpg', '.gif', '.bmp');
               my @exts = (@figexts,@texexts);
               my $ofilename = $filename;

               # Search if the registered images
               my $template = basename($filename, @exts);
               my %filenames = ();
               if ($self->{'images'}) {
                       my $ext;
                       for(my $k=0; $k<@{$self->{'includepaths'}}; $k++) {
                               my $path = $self->{'includepaths'}[$k];
                               for(my $j=0; $j<@{$self->{'images'}}; $j++)  {
                                       my $img = $self->{'images'}[$j];
                                       for(my $i=0; $i<@figexts; $i++)  {
                                               $ext = $figexts[$i];
                                               my $fullname = File::Spec->catfile($path,"$template$ext");
                                               $fullname = $self->_makeFilename($fullname,'');
                                               if (-f $fullname) {
                                                       $filenames{$fullname} = 0;
                                               }
                                       }
                                       for(my $i=0; $i<@texexts; $i++)  {
                                               $ext = $texexts[$i];
                                               my $fullname = File::Spec->catfile($path,"$template$ext");
                                               $fullname = $self->_makeFilename($fullname,'');
                                               if (-f $fullname) {
                                                       $filenames{$fullname} = 1;
                                               }
                                       }
                               }
                       }
               }

               if (!%filenames) {
                       # Search in the folder, from the document directory.
                       $template = File::Spec->catfile(dirname($ofilename), basename($ofilename, @exts));
                       my $ext;
                       for(my $i=0; $i<@figexts; $i++)  {
                               $ext = $figexts[$i];
                               my $fn = "$template$ext";
                               if (-f $fn) {
                                       $filenames{$fn} = 0;
                               }
                       }
                       for(my $i=0; $i<@texexts; $i++)  {
                               $ext = $texexts[$i];
                               my $fn = "$template$ext";
                               if (-f $fn) {
                                       $filenames{$fn} = 1;
                               }
                       }
               }

               if (!%filenames) {
                       printErr(formatText(_T('Picture not found: {}'), $texname));
               }
               else {
                       my $ext;
                       my @selectedName1 = ();
                       my $selectedName2 = undef;
                       foreach $filename (keys %filenames) {
                               $filename =~ /(\.[^.]+)$/s;
                               $ext = $1 || '';
                               $texname = $self->_uniq($filename, $ext).$ext;
                               if ($filenames{$filename}) {
                                       if (!@selectedName1) {
                                               @selectedName1 = ($texname,$filename);
                                       }
                               }
                               else {
                                       $self->{'data'}{'figures'}{$filename} = $texname;
                                       $selectedName2 = $texname;
                               }
                       }
                       if (@selectedName1) {
                               ($texname, $filename) = @selectedName1;
                               printDbg(formatText(_T('Embedding {}'), $filename));
                               my $filecontent = readFileLines("$filename");
                               # Replacing the filename in the TeX file
                               while (my ($localFile, $texfile) = each(%{$self->{'data'}{'figures'}})) {
                                       $filecontent =~ s/\Q$localFile\E/$texfile/g;
                               }
                               $prefix .="%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n".
                                      "%%% BEGIN FILE: ".basename($texname)."\n".
                                      "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n".
                                       "\\begin{filecontents*}{".basename($texname)."}\n".
                                       $filecontent.
                                       "\\end{filecontents*}\n".
                                       "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n\n\n";
                               $self->{'preamble'}{'filecontents'} = "\\usepackage{filecontents}";
                       }
                       elsif ($selectedName2) {
                               $texname = $selectedName2;
                       }
               }
       }
       else {
               $texname =~ /(\.[^.]+)$/s;
               my $ext = $1 || '';
               $texname = $self->_uniq($filename, $ext).$ext;
               if (!arrayContains(@texexts, $ext)) {
                       $self->{'data'}{'figures'}{$filename} = $texname;
               }
       }

       return ($texname,$prefix);
}

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

       if (            $macro eq '\\usepackage' || $macro eq '\\RequirePackage') {
               my $texname = $_[1]->{'text'};
               my $filename = $self->_makeFilename("$texname", '.sty');

               my $ret = '';

               if ($texname eq 'biblatex') {
                       if (!$self->{'usebiblio'}) {
                               my $filename = $self->_makeFilename($self->{'basename'}, '.bbl', '.tex');
                               if (-f "$filename") {
                                       printDbg(formatText(_T('Embedding {}'), $filename));
                                       $self->{'preamble'}{'filecontents'} = "\\usepackage{filecontents}";
                                       $ret .="%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n".
                                              "%%% BEGIN FILE: ".basename($filename)."\n".
                                              "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n".
                                               "\\begin{filecontents*}{".basename($filename)."}\n".
                                               readFileLines("$filename").
                                               "\\end{filecontents*}\n".
                                               "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n\n\n";
                               }
                               else {
                                       printErr(formatText(_T('File not found: {}'), $filename));
                               }
                       }
               }
               elsif ($self->_isDocumentFile($filename)) {
                       printDbg(formatText(_T('Embedding {}'), $filename));
                       $self->{'preamble'}{'filecontents'} = "\\usepackage{filecontents}";
                       $ret .="%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n".
                              "%%% BEGIN FILE: ".basename($filename)."\n".
                              "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n".
                               "\\begin{filecontents*}{".basename($filename)."}\n".
                               readFileLines("$filename").
                               "\\end{filecontents*}\n".
                               "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n\n\n";
               }
               $ret .= $macro;
               $ret .= '['.$_[0]->{'text'}.']' if ($_[0]->{'text'});
               $ret .= '{'.$texname.'}';
               return $ret;
       }
       elsif (         $macro eq '\\documentclass') {
               my $texname = $_[1]->{'text'};
               my $filename = $self->_makeFilename("$texname", '.cls');
               if ($self->_isDocumentFile($filename)) {
                       $texname = $self->_uniq($filename,'.cls');
                       $self->{'data'}{'cls'}{$filename} = "$texname.cls";
               }
               my $ret = $macro;
               $ret .= '['.$_[0]->{'text'}.']' if ($_[0]->{'text'});
               $ret .= '{'.$texname.'}';
               $ret .= "\n\n%%%%% AUTOLATEX PREAMBLE\n\n";
               return $ret;
       }
       elsif (         ($macro eq '\\includegraphics')
               ||      ($macro eq '\\includeanimatedfigure')
               ||      ($macro eq '\\includeanimatedfigurewtex')
               ||      ($macro eq '\\includefigurewtex')
               ||      ($macro eq '\\includegraphicswtex')) {
               my ($texname,$prefix) = $self->_findPicture($_[1]->{'text'});
               my $ret = $prefix.$macro;
               $ret .= '['.$_[0]->{'text'}.']' if ($_[0]->{'text'});
               $ret .= '{'.$texname.'}';
               return $ret;
       }
       elsif (         $macro eq '\\graphicspath') {
               my @paths = ();
               my $t = $_[1]->{'text'};
               while ($t && $t =~ /^\s*(?:(?:\{([^\}]+)\})|([^,]+))\s*[,;]?\s*(.*)$/g) {
                       my $prev = "$t";
                       (my $path, $t) = (($1||$2), $3);
                       push @paths, "$path";
               }
               unshift @{$self->{'includepaths'}}, @paths;
               return '\\graphicspath{{.}}';
       }
       elsif (         $macro eq '\\mfigure' || $macro eq '\\mfigure*' ||
                       $macro eq '\\mfiguretex' || $macro eq '\\mfiguretex*') {
               my ($texname,$prefix) = $self->_findPicture($_[2]->{'text'});
               my $ret = $prefix.$macro;
               $ret .= '['.$_[0]->{'text'}.']' if ($_[0]->{'text'});
               $ret .= '{'.$_[1]->{'text'}.'}';
               $ret .= '{'.$texname.'}';
               $ret .= '{'.$_[3]->{'text'}.'}';
               $ret .= '{'.$_[4]->{'text'}.'}';
               return $ret;
       }
       elsif (         $macro eq '\\msubfigure' || $macro eq '\\msubfigure*') {
               my ($texname,$prefix) = $self->_findPicture($_[2]->{'text'});
               my $ret = $prefix.$macro;
               $ret .= '['.$_[0]->{'text'}.']' if ($_[0]->{'text'});
               $ret .= '{'.$_[1]->{'text'}.'}';
               $ret .= '{'.$texname.'}';
               $ret .= '{'.$_[3]->{'text'}.'}';
               return $ret;
       }
       elsif (         $macro eq '\\include' || $macro eq '\\input') {
               my $filename = $self->_makeFilename($_[0]->{'text'},'.tex');
               my $subcontent = readFileLines($filename);
               $subcontent .= "\n%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n".
                              "%%% END FILE: $filename\n".
                              "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n\n";

               $parser->putBack($subcontent);
               return "\n\n".
                      "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n".
                      "%%% BEGIN FILE: $filename\n".
                      "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n";
       }
       elsif ($macro =~ /^\\bibliographystyle(.*)$/s ) {
               if ($self->{'usebiblio'}) {
                       my $bibdb = $1;
                       $bibdb = $self->{'basename'} unless ($bibdb);
                       my $texname = $_[0]->{'text'};
                       my $filename = $self->_makeFilename("$texname", '.bst');
                       if ($self->_isDocumentFile($filename)) {
                               $texname = $self->_uniq($filename,'.bst');
                               $self->{'data'}{'bst'}{$filename} = "$texname.bst";
                       }
                       my $ret = $macro;
                       $ret .= '{'.$texname.'}';
                       return $ret;
               }
               return '';
       }
       elsif ($macro =~ /^\\bibliography(.*)$/s ) {
               my $bibdb = $1;
               $bibdb = $self->{'basename'} unless ($bibdb);
               if ($self->{'usebiblio'}) {
                       my $texname = $_[0]->{'text'};
                       my $filename = $self->_makeFilename("$texname",'.bib');
                       if ($self->_isDocumentFile($filename)) {
                               $texname = $self->_uniq($filename,'.bib');
                               $self->{'data'}{'bib'}{$filename} = "$texname.bib";
                       }
                       my $ret = $macro;
                       $ret .= '{'.$texname.'}';
                       return $ret;
               }
               else {
                       my $bblFile = "$bibdb.bbl";
                       if (!File::Spec->file_name_is_absolute($bblFile)) {
                               $bblFile = File::Spec->catfile($self->{'dirname'}, "$bblFile");
                       }
                       if (-f "$bblFile") {
                               my $ret = "\n\n".
                                      "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n".
                                      "%%% BEGIN FILE: ".basename($bblFile)."\n".
                                      "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n";
                               $ret .= readFileLines("$bblFile");
                               $ret .= "\n%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n";
                               return $ret;
                       }
                       else {
                               printErr(formatText(_T('File not found: {}'), $bblFile));
                       }
               }
       }
       elsif (         $macro eq '\\addbibresource') {
               if ($self->{'usebiblio'}) {
                       my $texname = $_[1]->{'text'};
                       my $filename = $self->_makeFilename("$texname", '.bib');
                       if ($self->_isDocumentFile($filename)) {
                               $texname = $self->_uniq($filename,'.bib');
                               $self->{'data'}{'bib'}{$filename} = "$texname.bib";
                       }
                       my $ret = $macro;
                       $ret .= '{'.$texname.'}';
                       return $ret;
               }
               else {
                       return '';
               }
       }

       return $macro;
}

sub _outputString($$@) : method {
       my $self = shift;
       my $parser = shift;
       my $text = shift;
       if (defined($text)) {
               $self->{'data'}{'expandedContent'} .= $text;
       }
}

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'),
                       'dirname' => File::Spec->rel2abs(dirname($_[0])),
                       'file' => $_[0],
                       'output' => $_[1],
                       'images' => $_[2],
                       'includepaths' => [ File::Spec->curdir() ],
                       'usebiblio' => $_[3],
                       'outputString' => \&_outputString,
                       'expandMacro' => \&_expandMacro,
                       'discoverMacroDefinition' => \&_discoverMacroDefinition,
                       'preamble' => {},
                       'data' => {
                               'figures' => {},
                               'cls' => {},
                               'sty' => {},
                               'bib' => {},
                               'bst' => {},
                               'expandedContent' => '',
                               'uniq' => {},
                       }
               };
       }
       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>