Expandable author names from biblatex?

Expandable macros can be defined from unformatted bibliographic data. The \savename command simply copies an unformatted name list to a macro. These data can also be accessed via formatting directives defined with \DeclareNameFormat or \DeclareIndexNameFormat.

\DeclareIndexNameFormat is intended to write data to the index, but we can use it to pick up data from each name in the list. Inside its resulting directive, biber-only name delimiters (e.g. \bibinitperiod) expand to plain text. This is useful because biblatex punctuation commands are only defined within citation or bibliography commands.

The document below demonstrates a formatting directive getname that appends the first names (#3) (or first initials (#4) under the firstinits=true option setting), last name prefix (#5) and last name (#1) to the expandable macro \name. The directive is executed by the \getauthor citation command.

\documentclass{article}
\usepackage[american]{babel}
\usepackage[backend=biber,firstinits]{biblatex}

\newcommand*{\name}{}
\DeclareIndexNameFormat{getname}{%
  \def\addcomma{,}
  \def\space{ }
  \ifnumgreater{\value{listcount}}{\value{liststart}}
    {\ifnumless{\value{listcount}}{\value{liststop}}
       {\xappto{\name}{\addcomma\space}}
       {\ifnumgreater{\value{liststop}}{2}
          {\xappto{\name}{\finalandcomma}}
          {}%
        \xappto{\name}{\space\bibxstring{and}\space}}}
    {}%
  \ifblank{#4}   % See update below
    {}
    {\iffirstinits
       {\xappto{\name}{#4\space}}
       {\xappto{\name}{#3\space}}}%
  \ifblank{#5}
    {\xappto{\name}{#1}}
    {\xappto{\name}{#5\space#1}}}

\DeclareCiteCommand{\getauthor}
  {\undef\name
   \boolfalse{citetracker}%
   \boolfalse{pagetracker}}
  {\ifnameundef{author}
     {}
     {\ifboolexpr{ test {\ifnumgreater{\value{citecount}}{1}}
                   and not test {\ifundef{\name}} }
        {\gappto{\name}{; }}
        {}%
      \indexnames[getname][1-99]{author}}}
  {}
  {}

\addbibresource{biblatex-examples.bib}
\begin{document}
\getauthor{knuth:ct,vangennep}
\edef\temp{Some text \name}
\subsubsection*{\temp}
\getauthor{companion,aristotle:anima,ctan}
\subsubsection*{\MakeUppercase{\name}}
\textcite{cms,companion,aristotle:anima,ctan}
\end{document}

UPDATE Since the original answer the syntax of \DeclareIndexNameFormat has changed: instead of arguments #1 to #8 the full name is stored in #1 and can be accessed by different \namepart macros. The affected part of the code above works with the following changes:

\nameparts{#1}
\ifblank{\namepartgiveni}
  {}
  {\iffirstinits
     {\xappto{\name}{\namepartgiveni\space}}
     {\xappto{\name}{\namepartgiven\space}}}%
\ifblank{\namepartprefix}
  {\xappto{\name}{\namepartfamily}}
  {\xappto{\name}{\namepartprefix\space\namepartfamily}}}

Well, shiver me timbers! Getting this done was a PAIN!!

@Audrey already answered an hour ago, however, I also got to a solution in the meantime, so I'd just like to document it here...

To begin with - the most important part for me - here is the terminal output of the (not so) MWE, posted at end:

(./test.aux) (./test.bbl)
taA__Authorsen1__A.\spacefactor \blx@sf@dot ______
taB__Authorsen1__Author1______
ta --Author1 Authorsen1==
taA__Authorsen1a__A.\spacefactor \blx@sf@dot ______
taB__Authorsen1a__Author1a______
ta -- and Author1a Authorsen1a.==

= currentauthors =
==Author1 Authorsen1 and Author1a Authorsen1a.==

taA__Authorson2__A.\spacefactor \blx@sf@dot ______
taB__Authorson2__Author2______
ta --Author2 Authorson2.==

= currentauthors =
==Author2 Authorson2.==

taA__Authorsen3__A.\spacefactor \blx@sf@dot ______
taB__Authorsen3__Author3______
ta --Author3 Authorsen3==
taA__Authorsen3A__A.\spacefactor \blx@sf@dot ______
taB__Authorsen3A__Author3A______
ta --, Author3A Authorsen3A==
taA__Authorsen3B__A.\spacefactor \blx@sf@dot ______
taB__Authorsen3B__Author3B______
ta --, and Author3B Authorsen3B.==

= currentauthors =
==Author3 Authorsen3, Author3A Authorsen3A, and Author3B Authorsen3B.==


Underfull \hbox (badness 10000) in paragraph at lines 407--408

Names seem to be properly expanded as 'plain text'; however, the example also shows that this approach can still coexist with the usual biblatex typesetting:

test.png

(the main intent of the expandable process is simply to provide text characters; however in the MWE it also typesets (though not succesfully) simply as an example - otherwise, only a single command should be commented to suppress the typeset output, see code)

Discussion

A few words, while I can still remember... Basically, it was all spinning in circles and looking at endless traceon outputs, until one realizes that the \DeclareNameFormat is the main part where one could have those names at all; then by accident I tried to wrap an \edef over its inner macros:

%   \DeclareNameFormat{testauthors}{% now only this works
%   \typeout{ta==#1==#4==#5==#7==}
%   \typeout{ta==#1==#3==#5==#7==}
%   \edef\tmpa{
%   \iffirstinits
%     {\usebibmacro{name:first-last}{#1}{#4}{#5}{#7}}
%     {\usebibmacro{name:first-last}{#1}{#3}{#5}{#7}}%
%   \usebibmacro{name:andothers}
%   }
%   \typeout{ta--\tmpa==}
%   \tmpa
%   }%

... and while that failed - it also sparked the idea to wrap only the \usebibmacros in separate \edef (which is the basis of the current solution):

%   \DeclareNameFormat{testauthors}{% now only this works
%   \typeout{ta==#1==#4==#5==#7==}
%   \typeout{ta==#1==#3==#5==#7==}
%   
%   \iffirstinits
%     {\edef\tmpa{\usebibmacro{name:first-last}{#1}{#4}{#5}{#7}}}
%     {\edef\tmpa{\usebibmacro{name:first-last}{#1}{#3}{#5}{#7}}}%
%   \usebibmacro{name:andothers}
%   
%   \typeout{ta--\tmpa==}
%   \tmpa
%   }%

That didn't work very well either - but at least it started showing which commands are the unexpandable ones; so I could redefine them in my .tex file as unprotected commands. So again by a long process of traceing, I started copying existing functions from biblatex.sty into my .tex file. This part is again not trivial - because not all functions can be found defined verbatim in biblatex.sty; then one can use either \show in the .tex document itself - or texdef (usually when a definition is not shown with one method, it will be shown with one of the other two).

Then, one should keep in mind that a protected biblatex command like \usebibmacro{name:first-last} has an "internal" counterpart in \abx@macro@name:delim (and I still cannot tell how I figured out the proper relationships, apart from staring in logs and source files :)) But the internal commands could be protected as well - or if not, may make use of protected macros; so it takes a while to identify a "minimal" set of "hacked" definitions that are needed.

Another thing to keep in mind is that \DeclareNamesFormat will be called for each individual name in a citation; therefore, one cannot expect to "pull" a complete "names list" in one pass - in the MWE below, there is a "global" "variable" (\currentauthors), which is used to concatenate the contents generated in each pass - and should contain the complete author name list as expanded text, once the custom cite process is finished.

Also, I should note that wherever I could, I tried removing typesetting commands like \unhbox and such, as they are typically also non-expandable; there seems to be some calculation for interpunction in biblatex based on diverse skips - but their removal doesn't seem to have too much effect on the expanded names list.

I got to a critical point when I got this log output:

taA==Authorsen2==A.======
taB==Authorsen2==Author2======
ta --Author2 Authorsen2 ==
taA==Authorsen2a==A.======
taB==Authorsen2a==Author2a======
ta --\ifboolexpr {test {0}ortest 0}{, }{ and }Author2a Authorsen2a ==

... that meant that almost all I needed was expanded - except for a \ifboolexpr. First I thought of using boolexpr package, but that conflicts with etoolbox/biblatex ... Then it turns out that there is an expandable \ifboolexpe in etoolbox as well - and once that got replaced, I could in fact remove some superfluous redefinitions.

Once the whole thing started generating decent names (the rules biblatex uses to compose names lists seem to be preserved, also for the expanded text output), there was another problem: since I basically redefined "core" macros of biblatex, now I could have a custom cite which is expandable - but the "usual" non-expandable custom cite wouldn't work anymore!

That got solved by adding the prefix EX to all redefined macro names, to help distinguish them from the default ones - and that seems to allow for a mix of methods, that allow both usual typesetting of author names via biblatex; and also allow for expansion of the same.

Well, I guess that was all I could remember :) However, there are plenty of comments in the code - so hope it helps someone..

MWE Code

(build w/ pdflatex test.tex ; biber test ; pdflatex test.tex)

\documentclass{book}

  \usepackage{trace}

  \usepackage{filecontents}
  \begin{filecontents}{\jobname.bib}
  @misc{num01,
      title = {Title1},
      author = {Author1 Authorsen1 and Author1a Authorsen1a},
      howpublished = {12-23-2001},
      eprint = {MR001},
      eprinttype = {mrnumber}}
  @misc{num02,
      title = {Title},
      author = {Author2 Authorson2},
      howpublished = {12-23-2002},
      eprint = {002},
      eprinttype = {mrnumber}}
  @misc{num03,
      title = {Title XX3},
      author = {Author3 Authorsen3 and Author3A Authorsen3A and Author3B Authorsen3B},
      howpublished = {12-23-2003},
      eprint = {MR003 (aa)},
      eprinttype = {mrnumber}}
  @misc{num04,
      title = {Title},
      author = {Author4},
      howpublished = {12-23-2004},
      eprint = {004 (bb)},
      eprinttype = {mrnumber}}
  \end{filecontents}

  \usepackage[bibstyle=numeric,citestyle=numeric]{biblatex}
  \ExecuteBibliographyOptions{%
    abbreviate=false,%
    uniquename=allfull,%
    maxnames=99,%
    minnames=99,%
    backend=biber,%
  }
  \addbibresource{\jobname.bib}

  %\usepackage{boolexpr} %tlmgr install boolexpr ; expandable? yes, but:
  % overwrites the \ifboolexpr by etoolbox (loaded by biblatex),
  % and then everything breaks.. so cannot use


  \makeatletter


  % must keep, else appears as unexpanded in output
  % was \protected ; used \isdot
  %
  % (biid): dot in mathmode;
  %         \mathchardef\blx@sf@dot=1002;
  %         1002 abbreviation period (dot)
  %         typesetting related ?! don't use, it doesn't expand
  %         doesn't run in this example (we don't typeset anymore)?
  %         but it can kick in, if \abx@dot gets added here after fi
  %
  \def\EXblx@imc@isdot{%
  \ifnum\blx@spacefactor=\blx@sf@period
    %\spacefactor%                    (rtc)
    \blx@sf@dot %                     (biid)
  \fi
  }

  % must keep, to remove unexpanded typesettin cmds
  %
  \def\EXabx@dot{%
  \ifdim\lastkern>\z@\unkern\fi
  .%
  %\spacefactor%                      (rtc)
  %\blx@sf@dot%                       (rtc)
  }%

  % must keep, to remove unexpanded \unspace
  %
  \def\EXblx@addpunct#1{%
  %\unspace%                          (rtc)
  \ifnum\blx@spacefactor<\blx@sf@threshold@low
    \csuse{blx@qp@#1}\csuse{abx@#1}%
  \else
    \ifnum\blx@spacefactor>\blx@sf@threshold@high
      \csuse{blx@qp@#1}\csuse{abx@#1}%
    \else
      \ifcsdef{blx@pp@\the\csname blx@sf@#1\endcsname} %
        {\csuse{blx@qp@#1}\csuse{abx@#1}}
        {\csuse{blx@qp@#1}}%
    \fi
  \fi
  \csuse{blx@pq@#1}}

  % must keep, else appears as unexpanded in output
  % was \protected ; used \adddot
  %
  \def\EXblx@imc@adddot{%
  \EXblx@addpunct{dot}%
  \ifnum\blx@spacefactor=\blx@sf@period
    %\spacefactor%                    (rtc)
    %\blx@sf@dot%                     (rtc)
  \fi}

  % must keep, else "! Undefined control sequence."
  % was \protected ; used \addcomma
  %
  \def\blx@imc@addcomma{%
    \EXblx@addpunct{comma}}

  % was \newrobustcmd* ; used \adddot
  %
  \def\bibinitperiod{\EXblx@imc@adddot} %  ;

  % must keep, else appears as unexpanded in output
  % was \protected
  %
  \def\EXblx@imc@resetpunctfont{%
  \blx@ifpuncthook
    {\global\let\abx@puncthook\@firstofone}
    {}}

  % (rtc) removed typesetting cmd

  % must keep, else appears as unexpanded in output
  % was \newrobustcmd
  %
  \def\EXaddlowpenspace{%
  %\unspace%                          (rtc)
  \blx@postpunct%
  %\penalty\value{lownamepenalty}%    (rtc)
  \space%
  \EXblx@imc@resetpunctfont}

  % must keep, else appears as unexpanded in output
  % was \newrobustcmd
  %
  \def\EXbibnamedelimd{\EXaddlowpenspace} %

  % must keep, else appears as unexpanded in output
  %  was \protected
  %
  \def\EXaddspace{%
  %\unspace%    %                     (rtc)
  \blx@postpunct%
  \space%
  \EXblx@imc@resetpunctfont%
  }

  % leaving \def\finalnamedelim out would cause trouble;
  %  even if by default it if not protected..
  %  that is because here, we want to avoid use of
  %  protected \bibstring => instead, \blx@imc@bibxstring
  %
  \def\EXfinalnamedelim{% not protected - for ref..
  \ifnumgreater{\value{liststop}}{2}%
  {\finalandcomma}%
  {}%
  \EXaddspace%
  \blx@imc@bibxstring{and}%\bibstring{and}%
  \space%
  }

  \def\EXlbx@finalnamedelim#1{%
    \EXfinalnamedelim%
  }


  % redef without long -- now namebreak no complain:
  %
  % if this \def for namebreak is commented,
  % then starting to get in console:
  % "### semi simple group (level 3) entered at line 333 (\begingroup)"
  %
  % but must now re-define it along w/ nameparser, to allow
  % interface with normal work
  %
  \def\EXblx@namebreak#1&{}

  \def\EXblx@nameparser#1{%
    \ifblank{#1}
      {\EXblx@namebreak\relax}
      {\ifnum\c@listcount<\c@liststart
       \else
         \EXblx@nameparser@i#1%
       \fi
       \advance\c@listcount\@ne
       \ifnum\c@listcount>\c@liststop
         \expandafter\EXblx@namebreak
       \fi
       \EXblx@nameparser}}

  \def\EXblx@nameparser@i#1{%
    \ifblank{#1}
      {}
      {\setkeys{blx@opt@name}{#1}}% this
    \blx@theformat}

  % also had previously redefined here:
  %   \DeclareRobustCommand{\KV@prefix}{}
  %   \def\blx@namesetup@i#1{%
  %   \def\blx@namesetup@ii#1#2{%
  %   \def\blx@namecodes{%
  %   \def\blx@namesetup#1#2#3{%

  % originally, here \ifboolexpr was used;
  %  when used in expandable context; \ifboolexpr will fail with
  %  "! Argument of \@secondoftwo has an extra }" - UNLESS
  %  default paths (like {True}{False} for \ifnumless;
  %  and \ifmorenames must have one char only, {1}{0}) are added!
  %  only then, not getting the \@secondoftwo problem!
  % BUT: \ifboolexpr robust = \ifboolexpe expandable
  % if using \ifboolexpe - then should NOT have the added T/F branches!
  %
  \expandafter\def\csname EXabx@macro@name:delim\endcsname#1{%
    \ifnumgreater{\value{listcount}}{\value{liststart}}%
    {\ifboolexpe{%
        test {\ifnumless{\value{listcount}}{\value{liststop}}}%
        or%
        test {\ifmorenames}%% \blx@imc@ifmorenames
      }%
      {\multinamedelim}{\EXlbx@finalnamedelim{#1}}%
    }%
    {}%
    %\show\bibstring%\typeout{==name:delim==#1==}% bibstring=\protected macro:
  }


  % (nfl1): MUST have comment after cmd here; else a spurious space
  %         appears at end of last name! also removed \blx@imc@isdot (typeset)
  % (nfl2): added: hack to "hardcode" a dot after all of the names are done
  % (nfl3): \ifmorenames -> \blx@imc@ifmorenames
  %
  \expandafter\def\csname EXabx@macro@name:first-last\endcsname#1#2#3#4{%
    \csname EXabx@macro@name:delim\endcsname{#2#3#1}%
    \csname abx@macro@name:hook\endcsname{#2#3#1}%
    \ifblank{#2}%
    {}%
    {\mkbibnamefirst{#2}\EXblx@imc@isdot\EXbibnamedelimd}%
    \ifblank{#3}%
    {}%
    {\mkbibnameprefix{#3}\EXblx@imc@isdot%
      \ifpunctmark{'}{}{%
        \ifuseprefix{\bibnamedelimc}{\EXbibnamedelimd}}%
    }%
    \mkbibnamelast{#1}\EXblx@imc@isdot%
    \ifblank{#4}{}%
    {\EXbibnamedelimd\mkbibnameaffix{#4}}%  (nfl1)
    \ifboolexpe{%                         (nfl2)
        test {\ifnumless{\value{listcount}}{\value{liststop}}}%
        or%
        test {\ifmorenames}%              (nfl3)
      }%
      {}{\EXabx@dot}%
  }


  % (dnf1): expanding name:andothers is currently a problem;
  %          fails w/ "! Argument of \@secondoftwo has an extra }."
  %          but, 'and' in names gets added automatically - without name:andothers;
  %          name:andothers just if we want it shortened ("and others").
  %          and I don't need that currently..
  % (dnf2): perform concatenation here;
  % (dnf3): this "return" output of \tmpa also typesets;
  %          comment it out if typeset not desired
  %
  \DeclareNameFormat{testauthors}{%
    \typeout{taA__#1__#4__#5__#7__}
    \typeout{taB__#1__#3__#5__#7__}
    %\traceon
    \iffirstinits%
      {\edef\tmpa{\csname EXabx@macro@name:first-last\endcsname{#1}{#4}{#5}{#7}}}%
      {\edef\tmpa{\csname EXabx@macro@name:first-last\endcsname{#1}{#3}{#5}{#7}}}%
    %\edef\tmpb{\csname abx@macro@name:andothers\endcsname } % (dnf1)
    \csname abx@macro@name:andothers\endcsname

    \typeout{ta --\tmpa==}
    \global\edef\currentauthors{\currentauthors\tmpa} % (dnf2)
    \tmpa %                                             (dnf3)
  }%


  %\blx@namesetup: {#2}{#3}{#4} - start, stop, 'namelist';
  %            eg. {1}{99}{author} - but can do without
  %
  \def\getAuthsX{%
    \blx@getformat\blx@theformat{nfd}{testauthors}{author}%
    \blx@namesetup{}{}{author}%
    \expandafter\EXblx@nameparser\blx@thedata{}&%
  }

  \makeatother


  \def\getAuths{%
    \getAuthsX%
  }

  \newbibmacro*{myentrycite}{% custom
    \global\def\currentauthors{} % init empty string for concat
    \getAuths
  }

  \DeclareCiteCommand{\myentrycite}
    {}
    {\usebibmacro{myentrycite}}
    {}
    {}

  %%%%%%%%%%%%

  \DeclareNameFormat{normauthors}{%
  \iffirstinits
    {\usebibmacro{name:first-last}{#1}{#4}{#5}{#7}}
    {\usebibmacro{name:first-last}{#1}{#3}{#5}{#7}}%
  \usebibmacro{name:andothers}
  }%

  \newbibmacro*{normentrycite}{% custom
    \printnames[normauthors]{author}%
  }

  \DeclareCiteCommand{\normentrycite}
    {}
    {\usebibmacro{normentrycite}}
    {}
    {}


\begin{document}

  %\traceon

  \textit{Author names output of expandable cite commands:}

  % adding new cites will require a new biber run!
  \myentrycite{num01}
  \typeout{^^J= currentauthors =^^J==\currentauthors==^^J}

  \myentrycite{num02}
  \typeout{^^J= currentauthors =^^J==\currentauthors==^^J}

  \myentrycite{num03}
  \typeout{^^J= currentauthors =^^J==\currentauthors==^^J}

  \bigskip

  \textit{Author names output of usual cite command:} \\

  % will give "Runaway argument?" if all
  % defaults are reprogrammed; and not
  % handled with separate names!
  \normentrycite{num03}

\end{document}