I have to create certificates in Latex the format of which is already available with me. In this certificate, only the name of the participant changes and every other thing remains the same (including format, border, colors etc).

The list of participants is available in the form of a text file (participant.txt). This file contains one participant in each line.

How can I generate certificates automatically using latex.

My roadmap is as follows: - I will create a copy of TEX file; search for a specific line that contains the participant name, in this line I will replace the old participant by new_participant (by reading the participant.txt file) and compile the code. The same needs to be done for all the participants.

How to go on with this implementation ? Any help.

  • you don't say if you want one pdf with a certificate per page, or one pdf per certificate. In the first case you don't need to copy the tex file or use any external tools just a single latex run would do. – David Carlisle May 24 at 6:40
  • I implemented the “one output file for all participants” case. Can someone tell whether the fact that I had to use \endlinechar=-1 with \ior_map_inline:Nn is an expl3 bug or the result of a misderstanding of mine? Thanks! (I can make this a new question if you think it's appropriate.) – frougon May 24 at 7:06
  • Concerning the \endlinechar=-1, egreg replied below my answer (I had put the question there first). – frougon May 24 at 7:20
  • I would use a certificate template with %s for name and printf the actual names in there with a shell. At least bash has printf as a built-in. – Oleg Lobachev May 24 at 22:51

Here is a solution that creates one output file for all participants. You don't have to use any scripting for this, just one LaTeX run is enough. You may want to insert \newpage or \clearpage in the definition of \printCertificate if you wish to start a new page for each participant.

% I use these encodings in order to properly typeset “René Descartes”, which
% is in my participant.txt file. Use encodings appropriate for your situation,
% of course.


\msg_new:nnn { upsmakeparticlist } { file-not-found }
             { File~'\exp_not:n {#1}'~not~found. }

\cs_new_protected:Npn \ups_call_user_callback:Nn #1#2
    % Call the user callback (#1) with contents obtained from the file (#2).
    #1 {#2}

\cs_generate_variant:Nn \ups_call_user_callback:Nn { Nx }

\ior_new:N \g_ups_stream_ior

% #1: macro taking one argument (the participant name)
% #2: file containing one participant per line (blank lines are ignored)
\cs_new_protected:Npn \ups_list_participants:Nn #1#2
    \ior_open:NnF \g_ups_stream_ior {#2}
      { \msg_error:nnn { upsmakeparticlist } { file-not-found } {#2} }

    \ior_map_inline:Nn \g_ups_stream_ior
        \tl_if_eq:nnF {##1} { \par } % ignore blank lines
            % Calling \ups_call_user_callback:Nx will *not* trigger expansion
            % of user-provided contents from ##1, so this should be safe to use
            % (see the documentation of \tl_trim_spaces:n).
            \ups_call_user_callback:Nx #1 { \tl_trim_spaces:n {##1} }

    \ior_close:N \g_ups_stream_ior

% #1: macro taking one argument (the participant name)
% #2: file containing one participant per line (blank lines are ignored). Must
% be readable by TeX; for instance, it can live in the same directory as the
% .tex document or be somewhere in TEXINPUTS.
\NewDocumentCommand \participantlist { m m }
    \ups_list_participants:Nn #1 {#2}


% Will be our callback function that is called for each participant
  This is the certificate of #1, etc.\par


Input file participant.txt (may be in TEXINPUTS; for instance, can be in the same directory as the .tex file):

Isaac Newton
Pierre de Fermat
Karl Friedrich Gauss
Henri Lebesgue
William Rowan Hamilton
Georg Ferdinand Ludwig Philipp Cantor
René Descartes



  • 1
    I believe it's a stranded sentence. I'd prefer #1 { \tl_trim_spaces:n { ##1 } } to setting \endlinechar=-1. – egreg May 24 at 7:12
  • @egreg Thanks, I've applied your suggestion and will report a bug unless Someone of the LaTeX3 team tells me he is already on it. :-) – frougon May 24 at 7:19
  • 1
    See github.com/latex3/latex3/issues/369; there is a difference between \ior_map_inline:Nn and \ior_str_map_inline:Nn: the former does not ignore the \endlinechar, the latter does. – egreg May 24 at 7:22
  • 1
    \tl_trim_spaces:n returns its argument (after removing explicit leading and trailing spaces) surrounded by \exp_not:n, so it's safe in x expansion. – egreg May 24 at 8:24
  • 1
    @frougon Use variants: \exp_args:<specs> should be the exception, rather than the rule. – egreg May 24 at 10:51

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.