5

Problem statement

Under biblatex v3.11, I have defined a \DeclareCiteCommand, which includes both a \printfield{source} and a \printnames command (see MWE below).

The source field itself contains a citation (e.g., source = {\cite{Rossing1990}}), which I realize is probably problematic, but an essential aspect of what I am trying to achieve.

Depending on whether the citation in the source field contains a post-note or not, the \DeclareCiteCommand will not generate or generate a semicolon between \printfield{source} and \printnames.

  • I am hoping to get rid of the semicolon in the latter case, which used to be biblatex' default behavior a few versions ago (about 1-2 years, IIRC).
  • In addition, an explanation of why what I am observing occurs (with a reference to the biblatex documentation) would be appreciated.

Minimal (non-)working example (MWE)

main.tex file

\documentclass{article}

\usepackage[style=authoryear-comp]{biblatex}
\addbibresource{test.bib}

\DeclareCiteCommand{\mycite}{}{%
  \printfield{source}  \printnames[first-last]{author}
}{}{}

\begin{document}

This citation includes a semicolon between \verb|\printfield| and \verb|\printnames| (which I am trying to get rid of):\\
\mycite{MyImg}\\

This citation doesn't (which is what I want):\\
\mycite{MyImg2}\\

\end{document}

test.bib file

@image{MyImg,
    author = {{Addison-Wesley}},
    source = {\cite{Rossing1990}},
}

@image{MyImg2,
    author = {{Addison-Wesley}},
    source = {\cite[45]{Rossing1990}},
}

@book{Rossing1990,
    author = {Thomas D. Rossing},
    date = {1990},
}

Compilation

NOTE: Compilation of this MWE requires two biber runs. The following sequence works:

pdflatex main
biber main
pdflatex main
biber main
pdflatex main
  • 3
    Nesting citations is usually a problem indeed. You'd be better served by the "related" facilities biblatex provides. Do you know them? – gusbrs Jun 5 '18 at 23:14
  • 1
    I do and am a big fan. However, all this is about (mis)using biblatex to auto-generate figure captions :-] and I doubt that the related facilities would be sufficient to implement this. Essentially I am (mis)using biblatex as a database for my figures. – Florian H. Jun 5 '18 at 23:21
  • 2
    Mmh, indeed I was having a hard time figuring out what you were trying to achieve. I don't think that's the best tool for that... Out of the hat (without much knowledge of your actual requirements): dedicated macros? datatools? glossaries? – gusbrs Jun 5 '18 at 23:29
  • 1
    I'll happily look into such better-suited solutions at a later stage, but at the moment, I do primarily need a quick fix (and an explanation of what is going on in the first place, also to learn more about biblatex in general). – Florian H. Jun 5 '18 at 23:30
  • 1
    How about \newcommand{\mycaption}[3][]{\cite[#1]{#2} #3} to be then used as \mycaption[45]{Rossing1990}{Addison-Wesley}? – gusbrs Jun 5 '18 at 23:43
5

In the following I will use note instead of source (since source is not known in the default data model it will not be passed on to the .bbl file - I assume you have a custom .dbx set up).

Use \setunit{\addspace} instead of literal space

You only need a \setunit{\addspace} between the two \print... commands to get things working

%\RequirePackage{filecontents}
\begin{filecontents}{\jobname.bib}
@image{MyImg,
    author = {{Addison-Wesley}},
    note = {\cite{Rossing1990}},
}

@image{MyImg2,
    author = {{Addison-Wesley}},
    note = {\cite[45]{Rossing1990}},
}

@book{Rossing1990,
    author = {Thomas D. Rossing},
    date = {1990},
}
\end{filecontents}

\documentclass{article}

\usepackage[style=authoryear-comp]{biblatex}
\addbibresource{\jobname.bib}

\DeclareCiteCommand{\mycite}{}{%
  \printfield{note}%
  \setunit{\addspace}%
  \printnames[given-family]{author}%
}{}{}

\begin{document}

This citation includes a semicolon between \verb|\printfield| and \verb|\printnames| (which I am trying to get rid of):\\
\mycite{MyImg}\\

This citation doesn't (which is what I want):\\
\mycite{MyImg2}\\
\end{document}

enter image description here

Why does \setunit help?

The punctuation tracker

biblatex has a special approach to punctuation between fields or in general \print... commands. Punctuation should not be inserted directly, instead it s stored in a buffer and printed out only on demand. This asynchronous approach to punctuation vis-à-vis text makes it easy to avoid unwanted double punctuation.

You can read all about the punctuation tracker in §4.11.7 Using the Punctuation Tracker of the biblatex manual, the short version is as follows:

Whenever punctuation is desired, that punctuation is not inserted directly, it is wrapped in \setunit (the often used \newunit is equivalent to \setunit{\newunitpunct} with \newunitpunct defaulting to \addperiod\space). A typical snippet would be (from volume+number+eid in standard.bbx)

\printfield{volume}%
\setunit*{\adddot}%
\printfield{number}%
\setunit{\addcomma\space}%
\printfield{eid}

When \setunit is encountered (we'll come to the starred version \setunit* in a second), biblatex stores its argument in the punctuation buffer, the command itself prints nothing. The punctuation buffer can be overwritten by subsequent \setunits and so \setunit{X}\setunit{Y} leaves only Y in the buffer. The buffer is printed and emptied at the next \print... command that actually prints anything.

Suppose that in our example above, the entry has a volume and an eid field, but no number field. When the snippet is processed the volume will be printed, \setunit* inserts \adddot into the punctuation buffer. There is no number, so \printfield{number} does nothing. \setunit{\addcomma\space} sends \addcomma\space to the buffer and replaces its earlier value \adddot. Then \printfield{eid} prints the eid, but before that the punctuation buffer is printed and emptied. This results in

<volume>, <eid>

If a number had been present, we would have ended up with

<volume>.<number>, <eid>

The starred version \setunit* checks if the \print... command immediately preceding it printed something. If that is the case it behaves like \setunit, if not it discards it argument and does nothing. So in the example a difference would be visible if there was a number but no volume. Suppose that before the snippet above \printtext{Blah}\setunit{\addcomma\space} had inserted \addcomma\space into the punctuation buffer. With volume we would get

Blah, <volume>.<number>, <eid>

But if there is no volume and \setunit were used, the \adddot would overwrite the \addcomma\space to give

Blah.<number>, <eid>

With \setunit* nothing happens to the punctuation buffer if volume is missing and so we get

Blah, <number>, <eid>

because \addcomma\space remains in the buffer even after \printfield{volume} that did nothing and \setunit*{\adddot}.

Punctuation in authoryear-comp's \cite

The cite macros of authoryear-comp can be simplified to the following scheme (have a look at \DeclareCiteCommand{\cite} and \newbibmacro{cite} in authoryear-comp.cbx)

Loopcode

<Lots of `\print...` commands>
\setunit{\multicitedelim}

postcode

\iffieldundef{postnote}
  {}
  {\setunit{\postnotedelim}%
   \printfield{postnote}}

The loopcode is executed once for every entry you cite (so \cite{sigfridsson,nussbaum} would go through the loopcode once for sigfridsson and then for nussbaum), the postcode is executed at the end after the loop.

If you \cite something without a postnote the \cite command leaves a \multicitedelim in the punctuation buffer even after it is finished. This is because the last line of the loopcode is \setunit{\multicitedelim} and the postcode is effectively skipped if postnote is empty. Normally this is not a problem, since \cite is usually called in a context where the punctuation tracker does not control its surroundings as well - normally \cite is not followed by a \print... command - and so the buffer remains set, but is not printed. The next \cite (or any high-level biblatex command for that matter) clears the buffer before it does its work, so there is no harm in leaving the buffer non-empty after you are done. In your application, however, there is a \printfield after the \cite and so that \printfield sees the non-empty punctuation buffer and inserts it before it prints its contents.

If you have a postnote, then after the loopcode has done its thing and left \multicitedelim in the punctuation buffer, we enter the loopcode, take the second branch and are met with \setunit{\postnotedelim}. So \multicitedelim in the buffer is replaced with \postnotedelim, then \printfield{postnote} prints and empties the punctuation buffer and finally prints the postnote. Note that after this chain of events the punctuation buffer is empty. The \printfield that comes after the \cite has no punctuation to insert and simply prints its text.

Note that this situation came about specifically because of the implementation details of authoryear-comp's citation macros. This would not have happened with authoryear's citation commands. authoryear does not end its loopcode with \setunit{\multicitedelim}, the \multicitedelim is inserted with the sepcode argument to \DeclareCiteCommand. This can't be done for authoryear-comp and so there the (generally) stylistically questionable \setunit{\multictedelim} at the end of the loopcode had to be used.

Conclusion

Punctuation and spaces should always be handled by the punctuation tracker with \setunit. Never insert spaces or punctuation directly between \print... commands.

I should add that if at all possible \cite should be avoided in .bib entries and the bibliography. For some applications the related function might be a better fit, but I assume in other cases a work-around is not readily available and needs some thought.

What you are doing here in effect nests citation commands. Usually biblatex actively discourages nested citation commands (anyone who has tried \cite[\cite{nussbaum}]{sigfridsson} knows that), because they could lead to confusion for biblatex's many tracking features (after \cite[\cite{nussbaum}]{sigfridsson} what should "ibid." refer to? - and even if we decide that it would be sensible to mean sigfridsson that would be hard to enforce since nussbaum is actually processed last and biblatex's trackers have to be global, so no amount of grouping can help us). This might be less of an issue for this highly specialised citation command that is only used in a controlled environment, but it could definitely lead to confusion and unexpected behaviour in normal cite commands.

  • 1
    I'll explain this in more details later, since I should be getting some sleep now. But the basic idea is that cite commands of authoryear-comp leave punctuation in the buffer in case a second citation follows (as in \cite{sigfridsson,nussbaum}, a semicolon would be inserted here). There is no problem with the citation command leaving the buffer non-empty in a context outside of biblatex: It will not be printed. In our case another \print... follows and so the punctuation buffer is used. The solution was fortunately readily available: With \setunit{\addspace} we take over control. – moewe Jun 6 '18 at 0:28
  • 1
    For the time being §4.11.7 Using the Punctuation Tracker of the biblatex documentation is worth a read. It explains amongst others why \printfield{foo} \printfield{bar} is a bad idea and why \printfield{foo}\setunit{\addspace}\printfield{bar} is preferable. – moewe Jun 6 '18 at 0:35
  • This is fantastic, thanks! The only thing I do not quite understand yet is why the presence of a post-note prevents the semicolon as indicated in the second example of the MWE. – Florian H. Jun 6 '18 at 0:47
  • 1
    @FlorianH. I'll hopefully be able to explain that in the answer tomorrow, but the idea is that printing the postnote argument inserts a different punctuation in the buffer (\postnotedelim, in our case \addcomma\space) and prints it. After the \printfield{postnote} the punctuation buffer is then empty. – moewe Jun 6 '18 at 0:51
  • @FlorianH. I edited the answer with more detail. It has become quite long, but I hope I could address all the point in my comments here and give more background. If you have questions, don't hesitate to ask. – moewe Jun 6 '18 at 7:24

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.