8

I'd like to make a macro that expands to the contents of a file. Basically something like

\def{\mycommand}{\input{file.txt}}            % file.txt contains "Hello%"
\anothercommand{\mycommand}

should be completely equivalent to:

\def{\theircommand}{Hello}
\anothercommand{\theircommand}

The file shall be inputted when the macro is used, not when it is defined (since I'm generating it on the fly and it may change throughout the document). The reason I'm doing this is because I want to emulate \pdf@filemoddate, which I need, but which is not available in the old pdfLaTeX version I have at work. This is a well, almost minimal, non-working example with further information:

\documentclass{article}
\makeatletter

% This part is from epstopdf.  I can't change this.
% It just takes a string like D:20090114124316Z and
% formats it as a date.
% --------------------------------------------------
\def\ETE@Date#1\@nil{%
\ifx\\#1\\%
\else
  \ETE@@Date#1\@nil
\fi
}%
\def\ETE@@Date#1:#2#3#4#5#6#7#8#9{%
date: #2#3#4#5-#6#7-#8#9 %
\ETE@@Time
}%
\def\ETE@@Time#1#2#3#4#5#6#7\@nil{%
#1#2:#3#4:#5#6%
}%
\makeatother
% --------------------------------------------------

% some commands of increasing complexity
% this one just expands to the string I want out:
\def\blahone{D:20090114124316Z}
% this is the core functionality that I'm missing:
\def\blahthree{\input{temp.txt}}
% It seems to be not a problem with 'input', but an
% expansion problem, as this doesn't work either:
\def\blahtwo{\blahone}
% This is very close to the actual command I want to define
\def\myfiledate#1{%
  % in reality I call a little script with #1 here
  \immediate\write18{cat temp.txt > temp2.txt}%
  \input{temp2.txt}%
  \immediate\write18{rm temp2.txt}%
}

\begin{document}

\makeatletter

All macros work on their own.  There must not be spaces around the Xs: \\
X{\blahone}X \\
X{\blahtwo}X \\
X{\blahthree}X \\
X{\myfiledate{test.tex}}X \\

However, I can't use the results of the more complex ones as arguments: \\
\expandafter\ETE@Date\blahone\@nil \\

% doesn't work:
%\expandafter\ETE@Date\blahtwo\@nil \\
% need the 'romannumeral' trick to force full expansion:
\expandafter\ETE@Date\romannumeral-`X\blahtwo\@nil \\
% but that isn't an option.  I have code that calls
% \expandafter\ETE@Data\MACRO\@nil
% and I can only replace \MACRO, not change that code!

% doesn't work at all, no matter how many expandafters I try:
%\expandafter\ETE@Date\blahthree\@nil \\
%\expandafter\ETE@Date\filedate\@nil \\

\makeatother
\end{document}

The file test.txt contains only

D:20090114124316Z%

(with no newline at the end).


Update (what I'm trying to achive): I'm have a large document with lots of figures (eps) in it. When compiling it with pdfLaTeX, epstopdf converts the eps files only as needed to pdf. However, on my PC at work, the pdffilemoddate command is missing, and epstopdf can't tell if an eps file has been modified or not, so it insists on converting all figures again each time, which takes incredibly long. I'd like to have a \def\pdf@filemoddate#1{...} replacement that I can place in my header, and that permits epstopdf to function as intended.

4
  • 6
    This is the sole functionality of the catchfile package, May 30, 2013 at 16:15
  • catchfile is nice. However, I'd need to somehow pass a parameter to the setup code of CatchFileDef, to create a macro that takes an argument.
    – jdm
    May 30, 2013 at 16:35
  • the \readdef command of the readarray package does this. I'll look further at your MWE to see if it fits. May 30, 2013 at 16:44
  • @DavidCarlisle I just found out that catchfile won't work. It reads the file when I call \CatchFileDef{\macro}{file.txt}{}. If file.txt changes between invocations of \macro, it will have no effect.
    – jdm
    May 30, 2013 at 17:52

3 Answers 3

4

Of course you can use package catchfile, see David Carlisle's comment.

But you already have a script. Just extend it a little bit to output TeX code instead of the naked data. For example, the script is called with the file name as argument and it writes a file temp.tex that contains:

\mypdffilemoddate{<file>}{<year>}{<month>}{<day>}{<hour>}{<min>}{<sec>}{<timezone>}

Concrete example:

\mypdffilemoddate{foobar.tex}{2013}{05}{30}{19}{00}{00}{Z}

Macro \mypdffilemoddate is defined to your needs:

\newcommand*{\mypdffilemoddate}[8]{%
  % #1: file name
  % #2: year
  % #3: ... (see above)
  %
  % Examples, what can be done:
  % * date formatting
  % * storing the file date:
  \@namedef{mydate@#1}{<date>}
}

If the file date is stored, it can be used later by:

\@nameuse{mydate@}

After the script execution, the file is input by

\input{temp}

or

\InputIfFileExists{temp}{<file exists and is read>}{<file does not exist>}

The file temp.tex is read and \mypdffilemoddate is called.

5
  • That's a clever idea, but I don't think it will work here. I'm trying to craft a drop-in replacement for \pdf@filemoddate#1 (which is not available on one of my boxes) that will allow epstopdf and other packages to work properly. I won't call the macro myself, but the packages will.
    – jdm
    May 30, 2013 at 17:14
  • @jdm Then you are out of luck. The original \pdffilemoddate and \pdf@filemoddate with support for LuaTeX are both expandable. The simulation needs \immediate\write18 that prevents full expandability. Therefore also some macros of the packages that use \pdf@filemoddate might need to be rewritten. May 30, 2013 at 17:46
  • That's unfortunate... Still, catchfile manages to create fully expandable commands. I'm curious how that's done... probably that works because the file is read at definition time.
    – jdm
    May 30, 2013 at 17:50
  • @jdm Of course the commands are fine, but the definition of the macro and the setup are not expandable. For which package do you need a simulation? May 30, 2013 at 18:18
  • Its for epstopdf, for the function where it detects that a eps file has changed. I wrote a bit under my question about it. I'll probably be better off switching completely to PDF figures, or just compiling in draft mode when I'm using the crappy machine.
    – jdm
    May 30, 2013 at 19:12
4

Since the key issue seems to be the support of package epstopdf with all features especially option update with an older pdfTeX that lacks \pdffilemoddate (and \pdffilesize).

As in the question, the missing features are implemented using \immediate\write18 (shell escape feature). A script is run to provide the missing data, file date and file size. The output is written to a file and read in inside the TeX run.

With an uptodate pdfTeX, there is also a pipe feature that avoids the temporary scratch file. Also \pdfshellescape is available that reports the status of the shell escape feature. That can be used to issue a warning or error, if shell escape is not enabled. Because of an older pdfTeX in mind, the following example uses a temporary file and the shell escape feature must be enabled.

The following Perl script pdffiletools.pl expects two arguments, the first argument specifies the wanted data moddate or size. The second argument is the file name.

#!/usr/bin/env perl
use strict;
$^W=1;

my $prg_kpsewhich = 'kpsewhich';
my $kpsewhich_engine = 'pdftex';
my $kpsewhich_progname = 'pdflatex';
my $kpsewhich_format = 'tex';

my $syntax = "Syntax: $0 <action> <file>\n";

@ARGV == 2 or die $syntax;

my $action = shift;
my $arg_file = shift;

my @cmd = (
  $prg_kpsewhich,
  "-engine=$kpsewhich_engine",
  "-progname=$kpsewhich_progname",
  "-format=$kpsewhich_format",
);
my $file = `@cmd '$arg_file'`;
if ($? == -1) {
    die "!!! Error: Failed to execute ($prg_kpsewhich): $!\n";
}
elsif ($? & 127) {
    die sprintf "!!! Error: Child ($prg_kpsewhich) died "
                . "with signal %d %s coredump!\n",
                ($? & 127), ($? & 128) ? 'with' : 'without';
}
elsif ($? != 0) {
    die printf "!!! Error: Child ($prg_kpsewhich) exited with value %d!\n",
        $? >> 8;
}

chomp $file;
-f $file or die "!!! Error: File not found ($arg_file)!\n";

if ($action eq 'moddate') {
    my ($sec, $min, $hour, $mday, $mon, $year) = gmtime((stat($file))[9]);
    printf "D:%04d%02d%02d%02d%02d%02dZ",
        $year + 1900, $mon + 1, $mday, $hour, $min, $sec;
    exit(0);
}

if ($action eq 'size') {
    print ((stat($file))[7]);
    exit(0);
}

die "!!! Error: Unknown action ($action)!\n";

__END__

Remarks:

  • The file is found via kpsewhich with some options set to mimic the way pdflatex finds the files in \pdffilemoddate and \pdffilesize.
  • The file date uses GMT as time zone for simplicity. A more luxurious version would use local date with appended time zone specification.
  • The result is printed to the standard output with exit code 0.
  • Errors are printed to the standard error stream and the exit code is set.
  • Tested under Linux.

The following test file uses the script above to add the missing features of the older pdfTeX for package epstopdf:

\documentclass{article}

\usepackage{pdftexcmds}
\makeatletter
% \let\pdf@filemoddate\relax% testing with newer pdfTeX
% \let\pdf@filesize\relax% testing with newer pdfTeX
\@ifundefined{pdf@filemoddate}{%
  \newcommand*{\pdf@filemoddate}[1]{%
    \romannumeral-`\x
    \@ifundefined{filemoddate@#1}{}{\@nameuse{filemoddate@#1}}%
  }%
  \newcommand*{\pdf@filesize}[1]{%
    \romannumeral-`\x   
    \@ifundefined{filesize@#1}{}{\@nameuse{filesize@#1}}% 
  }%  
  \RequirePackage{graphicx}\relax
  \RequirePackage{epstopdf}\relax
  \RequirePackage{catchfile}\relax
  \newcommand*{\setpdf@filemoddate}[1]{%
    \immediate\write18{%
      ./pdffiletools.pl moddate '#1'>tmp-pdffiletools.txt%
    }%
    \CatchFileEdef{\setpdf@catch}{tmp-pdffiletools.txt}{%
      \@makeother\D
      \@makeother\:%
      \@makeother\Z
      \@makeother\+%
      \@makeother\-%
      \@makeother\'%
      \endlinechar=-1\relax
    }%
    \expandafter\let\csname filemoddate@#1\endcsname\setpdf@catch
  }%  
  \newcommand*{\setpdf@filesize}[1]{%
    \immediate\write18{%
      ./pdffiletools.pl size '#1'>tmp-pdffiletools.txt%
    }%
    \CatchFileEdef{\setpdf@catch}{tmp-pdffiletools.txt}{%
      \endlinechar=-1\relax
    }%
    \expandafter\let\csname filesize@#1\endcsname\setpdf@catch
  }%
  %
  \newcommand*{\org@ETE@Make}{}%
  \let\org@ETE@Make\ETE@Make
  \renewcommand*{\ETE@Make}[2]{%
    \ifETE@update
      \setpdf@filemoddate{#1}%
      \setpdf@filemoddate{#2}%
    \fi
    \org@ETE@Make{#1}{#2}%
  }%
  % 
  \newcommand*{\org@ETE@DefCommandLine}{}%
  \let\org@ETE@DefCommandLine\ETE@DefCommandLine
  \renewcommand*{\ETE@DefCommandLine}{%
    \setpdf@filemoddate{\SourceFile}%  
    \setpdf@filemoddate{\OutputFile}%  
    \setpdf@filesize{\SourceFile}%     
    \setpdf@filesize{\OutputFile}%     
    \org@ETE@DefCommandLine
  }%
}{% 
  \usepackage{graphicx}%
  \usepackage{epstopdf}%
}
\makeatother

\begin{document}
\includegraphics{test-image.eps}
\end{document}

Remarks:

  • Shell escape needs to be enabled (pdflatex -shell-escape).
  • The script is put into the current directory and therefore called as ./pdffiletools.pl. If it is installed as pdffiletools in a directory of environment variable PATH, then it is called as pdffiletools.
  • \setpdf@filemoddate{<file>} and \setpdf@filesize{<file>} call the script and store the result in macros \filemoddate@<file> and \filesize@<size>. This allows the expandable definition of \pdf@filemoddate{<file>} and \pdf@filesize{<file>}.
  • The \romannumeral trick keeps a generic property of package pdftexcmds that \pdf@filemoddate and \pdf@filesize can be expanded in exact two expansion steps.
  • Instead of the method in my other answer I have used package catchfile to provide a use case example for this method.
  • The original \pdffilemoddate and \pdffilesize return character strings, where the characters have catcode "other" (12). This is the reason for the catcode settings in the setup part of \CatchFileEdef.
  • Both \CatchFileDef and \CatchFileEdef can be used here. The implementation for the latter is easier and shorter.
  • The temporary file can be removed automatically, for example (Linux command name for deletion is rm and del for Windows):

    \usepackage{atveryend}
    \AtVeryEndDocument{%
      \IfFileExists{tmp-pdffiletools.txt}{%
        \immediate\write18{rm tmp-pdffiletools.txt}%
      }{}%
    }
    
3

The readarray package I think can do what you are asking. To show it, I use its \readdef command to input the file contents and place it into a token. Then I pass it to a string parser to grab off bits of it.

\documentclass{article}
\usepackage{readarray}
\usepackage{stringstrings}
\begin{document}
\readdef{temp.txt}{\blahfour}
X{\blahfour}X

\substring{\blahfour}{3}{6}

\substring{\blahfour}{$-6}{$}

\end{document}

enter image description here

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .