11

I am busy with a document where I need to add an Appendix with a list of names and short biographies (about a paragraph each).

I would be happier if I can do that as I describe the person in the relevant chapters save that to a macro and then add at the end. Similar to a Bibliography.

Is there a package that can do this or a way to build such macros?

alif
  • 255

3 Answers3

12

I would take a slightly different approach than Seamus.

We first define a list to hold all the names. The list is to be delimited by commas:

\def\alist{}% {George, Martin, Seamus, Yiannis}

We then define helper macros to add a person to the list. At the same time we create a command on the fly to hold the Bio. For example typing \Mary will typeset the Bio for the person.

To print the Bios in the appendix we simply type,

\PrintBios

We also add a few helper functions to keep the list sorted, so that we can print them alphabetically. These are borrowed for the listings package (just a BubbleSort).

To loop through the list we use the @for from the LaTeX kernel.

Here is a minimal that includes all these. The Bios are just paragraphs from the lipsum package.

\documentclass[11pt]{book}
\usepackage{lstdoc,lipsum}
\begin{document}
\makeatletter
\def\alist{}

\let\sort\lst@BubbleSort 
\def\addtolist#1#2{
  \lst@lAddTo\alist{#2}
}

\long\gdef\addPerson#1#2{\addtolist\alist{#1,}}

\def\AddBio#1#2{%
\long\expandafter\gdef\csname#1\endcsname{\textbf{#1}: #2}
\addPerson{#1}{#2}
\sort\alist
}

\def\PrintBios{%
  \@for \i:=\alist\do{%
  \csname\i\endcsname}
}
%example
\AddBio{Yiannis}{\lipsum[2]}
\AddBio{Mary}{\lipsum[3]}
\AddBio{Ann}{\lipsum[1]}
% print the biographies
\PrintBios
\makeatother
\end{document}

I am not too sure how fast it will be for hundreds of Bios, but for about 100-200 the compiling time was imperceptible.

yannisl
  • 117,160
5

Here's an example of using the newfile package to achieve this. It could obviously do with some tweaking. A lot of this is borrowed from answers to this question. Also thanks to Martin Scharrer for comments.

The idea is to write each author bio to a file and then read them all in at the end.

\documentclass{book}
\usepackage{newfile}
\usepackage{lipsum}
\newoutputstream{bios}
\openoutputfile{\jobname.bio}{bios}
\newcommand\styleauthor[1]{{\bfseries \begin{center}#1\end{center}}}
\newcommand\authorbio[2]{%
    \addtostream{bios}{%
      \unexpanded{%
        \styleauthor{#1}%
        \par
        #2
        \vspace{\baselineskip}}
    }
  }
\begin{document}
\chapter{Foo}
\authorbio{A. Author}{Here is a short bio on Albert Author

\lipsum[2]}

\chapter{Bar}

\authorbio{B. Buthor}{Here is a short bio on Brian Buthor

This allows newlines. Sweet.}

\appendix

\chapter{Author bios}
\closeoutputstream{bios}
\input{\jobname.bio}
\end{document}
Seamus
  • 73,242
  • This only works for single paragraphs... – Seamus Apr 08 '11 at 16:14
  • @Seamus no it doesn't. You fixed it, remember? – Seamus Apr 08 '11 at 16:25
  • @Seamus: Why shouldn't it work with more than one paragraph? It works fine in my tests. The only trouble I see if \par is redefined in a fragile way. Which can be fixed using: \newcommand\authorbio[2]{{\let\par\relax\addtostream{bios}{\noexpand\item[#1] #2}}} – Martin Scharrer Apr 08 '11 at 16:27
  • @Martin Scharrer I hadn't actually thought it through. I was covering my back. It think the way the updated solution works should be even more robust and more easily customisable... – Seamus Apr 08 '11 at 16:29
  • @Seamus: Why are you using \tempauthor? – Martin Scharrer Apr 08 '11 at 16:31
  • @Martin I thought it might be more robust than having paragraph breaks in the argument of the macro. But I didn't actually try out the simpler solution... – Seamus Apr 08 '11 at 16:33
  • 1
    @Seamus: Also i would recommend using \protect instead of \noexpand. It's much saver in writes. Here it might be ok, but e.g. \addtocontents writes the stuff first into the .aux file and then into the target file like .toc. Therefore you need more than one \noexpand. The \protect is then \noexpand\protect\noexpand :-) – Martin Scharrer Apr 08 '11 at 16:34
  • @Martin but I just tested it and it does seem to work without it... I'm still getting the hang of when you are allowed \par and when you aren't. It breaks in frameboxes, for example... – Seamus Apr 08 '11 at 16:35
  • Just use eTeX's \unexpanded: \newcommand\authorbio[2]{% \addtostream{bios}{\unexpanded{\styleauthor{#1}\par#2\vspace{\baselineskip}}}% }. The current code breaks when the \lipsum is expanded. It's fragile I suppose. Using \unexpanded would make it much more user-friendly. Otherwise you should write \long\def\tempauthor{#2}% instead of \def\tempauthor{#2}. – Martin Scharrer Apr 08 '11 at 16:39
  • @Martin thanks for the comments. I changed to \protect but I had to add a {} after the \par otherwise it complained of Undefined control sequence \parHere – Seamus Apr 08 '11 at 16:39
  • @Seamus: \protect seems in this case to be set to \string which doesn't add a space after it like \noexpand does. – Martin Scharrer Apr 08 '11 at 16:43
  • @Martin I didn't know about \unexpanded. That does seem to make for easier code. – Seamus Apr 08 '11 at 16:47
  • @Seamus: I can't follow. It removes the need to use \protect or \noexpand in the second argument. Also, please add a % after each line which ends with {, otherwise you will get a space there. – Martin Scharrer Apr 08 '11 at 17:30
  • @Martin good point, although I don't think the difference is noticeable... – Seamus Apr 08 '11 at 17:34
  • an alternative to \par is \endgraf. this tends to work in situations where the much-reformulated-by-latex \par causes problems. – barbara beeton Apr 08 '11 at 17:46
  • @Seamus: like I said: without it, it breaks at \lipsum for me. – Martin Scharrer Apr 08 '11 at 17:49
3

Just for fun, here's Yiannis' answer reimplemented in LaTeX3. The idea is exactly the same: the \AddBio command saves the author name and bio to macros, and uses the sort argument to name them. The sort is also what the authors are sorted by.

This makes use of egreg's solution to how to sort strings in an l3 way. (the code has the answer linked in a comment).

\documentclass{article}
\usepackage{xparse,expl3,kantlipsum,l3sort}



\ExplSyntaxOn
\seq_new:N \l_authorlist_seq

% Sort, Author Name, Bio
\NewDocumentCommand{\AddBio}{mm+m}{
  \tl_new:c {l_#1_name_tl}
  \tl_gset:cn {l_#1_name_tl} {#2}
  \tl_new:c {l_#1_bio_tl} 
  \tl_gset:cn {l_#1_bio_tl} {#3}
  \seq_gput_right:Nn \l_authorlist_seq {#1}
}

% From egreg's answer: http://tex.stackexchange.com/a/52967/215

\prg_new_conditional:Npnn \string_compare:nnn #1 #2 #3 {TF}
  {
   \if_int_compare:w \pdftex_strcmp:D {#1}{#3} #2 \c_zero
    \prg_return_true:
   \else:
    \prg_return_false:
   \fi
  }

\NewDocumentCommand{\sortauthors}{ }{
  \seq_sort:Nn \l_authorlist_seq {
    \string_compare:nnnTF {##1} {>} {##2} {\sort_reversed:} {\sort_ordered:}
  }
}

\NewDocumentCommand{\PrintBio}{m}{
  \textbf{\tl_use:c {l_#1_name_tl}:~} \tl_use:c{l_#1_bio_tl}\par
}

\NewDocumentCommand{\PrintBios}{}{
  \sortauthors
  \seq_map_inline:Nn \l_authorlist_seq {\PrintBio{##1}}
}
\ExplSyntaxOff
\begin{document}

  \AddBio{foo}{Jethro Foo}{\kant[1-2]}
  \AddBio{bar}{Horatio Bar}{\kant[2-3]}
  \AddBio{kant}{Immanuel Kant}{\kant[5]}

\PrintBios
\end{document}

I'm sure some of the commands and so on don't live up to the macro naming conventions of LaTeX3, but it's a proof of concept...

Seamus
  • 73,242
  • I should have actually used l3prop rather than rely on token lists. I did it properly here about line 93. – Seamus May 18 '13 at 12:18
  • \sort_ordered: and \sort_reversed: are deprecated as of 2018-12-31; the right functions are \sort_return_same: and \sort_return_swapped: – egreg Dec 27 '18 at 22:47