2

I'm taking a beating from biblatex's punctuation tracker, and thought I'd better ask for some assistance.

I'm trying to create a macro which is meant to be used as a general flag that I have translated a certain quotation. The natural place for this would be the citation postnote, but I wouldn't want to overdo it, and I think it is enough to do it every chapter, or section.

So, I came up with the following macro:

\newcounter{mytranslationcount}[section]
\newrobustcmd*{\mytranslation}{%
  \ifnum\value{mytranslationcount}=0
    \addcomma\addspace\printtext{my translation, as will be all following ones
      in a foreign language}%
    \stepcounter{mytranslationcount}%
  \fi}

It mostly works, but not quite. It does print the intended text when it should. However, it messes with biblatex's punctuation tracker, and this results in undesired output when the macro prints nothing, and there is nothing else in the postnote. In this case, the printing of the postnote, which from biblatex's point of view is not empty, triggers the punctuation tracker and we get a comma. And, as nothing else comes after it, we also lose the final period (in the case of a footnote).

A MWE:

\documentclass{article}

\usepackage[style=authoryear,autocite=footnote]{biblatex}

\addbibresource{biblatex-examples.bib}

\newcounter{mytranslationcount}[section] \newrobustcmd*{\mytranslation}{% \ifnum\value{mytranslationcount}=0 \addcomma\addspace\printtext{my translation, as will be all following ones in a foreign language}% \stepcounter{mytranslationcount}% \fi}

\begin{document}

\section{Section 1}

% Looks good. \autocite[\pnfmt{3333-3345}\mytranslation]{sigfridsson}

% Also good. \autocite[\pnfmt{3333-3345}\mytranslation]{sigfridsson}

\section{Section 2}

% Still good. \autocite[\mytranslation]{sigfridsson}

% This is the offending one. When there is nothing else in the postnote and % \mytranslation prints nothing, biblatex has already called in the postnote % punctuation from the tracker, so that we get an unwanted comma and lose an % wanted period. \autocite[\mytranslation]{sigfridsson}

\end{document}

I was hoping for one such macro I could use like this in the postnote, for the semantic value of doing it this way. Given this hope, I haven't yet tried to resort to \AtNextCite(key), or to tampering with the postnote macro directly, but I'm all ears...

gusbrs
  • 13,740
  • Don't think this is a punctuation tracker issue as such. As you say, in \autocite[\mytranslation]{sigfridsson} the postnote is not (seen/checked as) empty, even if \mytranslation ends up printing nothing, so the right thing to do for biblatex is to print the postnote delimiter and then the postnote (which here prints nothing). – moewe Apr 21 '21 at 20:13
  • Hi @moewe! Yes, that's what I also understand is happening. And I also don't think that is unexpected from biblatex's side. But... do you see a way around it without "deep hacks"? – gusbrs Apr 21 '21 at 20:15
  • My best take so far: patch the postnote bibmacro, and append something which is printed conditionally to a boolean which I could set \AtNextCite. But that's not a nice way to use it in the document. – gusbrs Apr 21 '21 at 20:17
  • @gusbrs: From your example it seems like you'll always have \mytranslation as part of the optional argument to \autocite. Correct? – Werner Apr 21 '21 at 20:24
  • @Werner Not always, only when I have translated the quotation to which the citation refers to. – gusbrs Apr 21 '21 at 20:25
  • Very tricky. To find out that \mytranslation doesn't print anything in those cases, you actually have to execute the macro. But the usual \iffieldundef test that is employed here is much simpler than that and doesn't expand the macros at all. What you could do instead (and in my book that would probably already count as hack) is to print the postnote and measure its width: If the width is 0pt, there postnote is empty. ... – moewe Apr 21 '21 at 20:34
  • ... Of course in your case that is problematic because you're working with a counter and the measuring step increases the counter, meaning that in the subsequent printing step the counter is off. (This can be fixed on an ad-hoc basis, but shows that a general solution is very tricky.) – moewe Apr 21 '21 at 20:34
  • @moewe I see, perhaps this use in the postnote will end up prohibitive in the end. The counter was just a way to improvise a "chapter/section hook", as I was surprised to find no hook for that. But, indeed, it poses that additional challenge to measure its width. And, yes, also in my book it counts as a hack. But, I'm looking for ideas and, as I've said, I'm all ears. – gusbrs Apr 21 '21 at 20:46
  • Just a comment: there should be no % after 0 in the code or the macro \addcomma would be untimely expanded. – egreg Apr 21 '21 at 21:38
  • @egreg Thanks! That's somewhat beyond my "expansion-fu", but I'll fix it. – gusbrs Apr 21 '21 at 21:40
  • @moewe Following your idea, I came up with \AtEveryCitekey{\setbox0=\hbox{\thefield{postnote}\unskip}\ifdim\wd0=0pt \clearfield{postnote} \fi \booltrue{mytranslationbool}}, and using \ifbool{mytranslationbool}{\stepcounter{mytranslationcount}}{} to set the counter in the macro. Is that what you had in mind? It is not really pretty, but seems to be working. WDYT? Particularly, would you say that, even if not pretty, it is robust? – gusbrs Apr 22 '21 at 02:10
  • @egreg May I ask you a side question? Now, with the new hook system in place, is there a reason not to have hooks on the sectioning commands? Is it the "principle of least tampering with the standard classes"? Or "no one has done it / thought about it" yet? It would be really nice to have them... – gusbrs Apr 22 '21 at 19:06
  • @gusbrs I'm afraid it's a very delicate aspect. There's the risk of breaking several documents that rely on the standard definition of \@startsection and related commands. – egreg Apr 22 '21 at 20:22
  • @egreg I was afraid it was a concern for stability which is, of course, due. Thanks for answering. – gusbrs Apr 22 '21 at 20:29

2 Answers2

1

As already discussed in the question itself and the comments, the problem here is that for biblatex it looks like \mytranslation is a non-empty postnote even if the command prints nothing when it is actually executed. (biblatex uses etoolbox's \ifblank test to check for emptyness.)

The only solution that I could come up with is to change the emptyness check to execute the postnote and measure its printed width. If it is zero, the postnote is empty, if the width is non-zero the postnote is not empty.

This then means that the postnote code is executed twice (once for measuring and once for the actual printing), so we have to be careful with counters. In the MWE I below I solved this by introducing a toggle that can be used to check if we are in the measuring or printing step.

biblatex-apa already has some code to measure the "printed width", so I took most code from there. It is not as simple as just doing \setbox0=\hbox{#1} because we need to make sure not to upset the punctuation tracker.

\documentclass{article}

\usepackage[style=authoryear,autocite=footnote]{biblatex}

\addbibresource{biblatex-examples.bib}

\newtoggle{blx@measuringstep}

\newcounter{mytranslationcount}[section] \newrobustcmd*{\mytranslation}{% \ifnumgreater{\value{mytranslationcount}}{0} {} {\addcomma\addspace\printtext{my translation, as will be all following ones in a foreign language}% \iftoggle{blx@measuringstep} {} {\stepcounter{mytranslationcount}}}}

% Thanks to egreg from https://tex.stackexchange.com/a/53091 % for this test for expanded emptiness so that we can easily opt to not print parens around nothing % Without this, it is very messy - you have to test all potential fields for defness first and this % is messy because the fields in the additional info vary betwee entrytypes \makeatletter \def\foreverunspace{% \ifnum\lastnodetype=11 \unskip\foreverunspace \else \ifnum\lastnodetype=12 \unkern\foreverunspace \else \ifnum\lastnodetype=13 \unpenalty\foreverunspace \fi \fi \fi }

% we need a way to save the state of the punctuation buffer % cf. \blx@initunit in biblatex.sty for what we need to copy

% this uses the internal implementation of etoolbox toggles % fingers crossed no one messes with it \newrobustcmd*{\blx@savetoggle}[1]{% \csletcs{apablx@savedtoggle@#1}{etb@tgl@#1}}

\newrobustcmd*{\blx@restoretoggle}[1]{% \csletcs{etb@tgl@#1}{apablx@savedtoggle@#1}}

\newrobustcmd*{\blx@savepunctstate}{% \blx@savetoggle{blx@block}% \blx@savetoggle{blx@unit}% \blx@savetoggle{blx@insert}% \blx@savetoggle{blx@lastins}% \blx@savetoggle{blx@keepunit}% \let\apablx@savd@unitpunct\blx@unitpunct \let\apablx@savd@puncthook\abx@puncthook}

\newrobustcmd*{\blx@restorepunctstate}{% \global\blx@restoretoggle{blx@block}% \global\blx@restoretoggle{blx@unit}% \global\blx@restoretoggle{blx@insert}% \global\blx@restoretoggle{blx@lastins}% \global\blx@restoretoggle{blx@keepunit}% \global\let\blx@unitpunct\apablx@savd@unitpunct \global\let\abx@puncthook\apablx@savd@puncthook}

% printtext that checks if it would print anything \newrobustcmd{\ifprintempty}[1]{% \blx@savepunctstate \toggletrue{blx@measuringstep}% \setbox0=\hbox{#1\foreverunspace}% \togglefalse{blx@measuringstep}% \blx@restorepunctstate \ifdimequal{\wd0}{\z@}}

\newrobustcmd{\iffieldempty}[1]{% \iffieldundef{#1} {@firstoftwo} {\expandafter\expandafter \expandafter\ifprintempty \expandafter\expandafter \expandafter{\csname abx@field@#1\endcsname}}} \makeatother

\renewbibmacro*{postnote}{% \iffieldempty{postnote} {} {\setunit{\printdelim{postnotedelim}}% \printfield{postnote}}}

\begin{document}

\section{Section 1}

% Looks good. \autocite[\pnfmt{3333-3345}\mytranslation]{sigfridsson}

% Also good. \autocite[\pnfmt{3333-3345}\mytranslation]{sigfridsson}

\section{Section 2}

% Still good. \autocite[\mytranslation]{sigfridsson}

% OK \autocite[\mytranslation]{sigfridsson}

\end{document}

4 Sigfridsson and Ryde 1998.

moewe
  • 175,683
  • Wow! Even trickier than I was supposing. I did not anticipate the need to preserve the punctuation buffer state, but indeed. Thank you very, very much! And, I cannot but ask/suggest, do you think the procedure is robust enough for it to be fit to provide \iffieldempty upstream? – gusbrs Apr 22 '21 at 11:04
  • I went late last night trying out stuff on this. One promising approach I found is to try to clear the postnot field \AtEveryCitekey when appropriate (being it the postnote, unfortunately we don't get to enjoy the power of SourceMap...). The best I could come up with \ifboolexpr{ ( test {\iffieldequalstr{postnote}{\mytranslation}} and not test {\ifnumequal{\value{mytranslationcount}}{0}} ) }, and clearing the field when this is true. This is, of course, not fully general, as we can only have one such macro there, and I don't know how fragile the test with \iffieldequalstr is... – gusbrs Apr 22 '21 at 11:20
  • ... Another approach went: \AtEveryCitekey save the field to a macro with \savefield, patch that macro removing \mytranslation from it, set a boolean flag to be used in \xapptobibmacro{postnote} if the patch was sucessfull, and restore the postnote from the patched macro. But it was getting sort of too hacky already. – gusbrs Apr 22 '21 at 11:23
  • 1
    Even with \renewcommand\mytranslation{}\autocite[\mytranslation]{sigfridsson} the postnote doesn't count as empty (without your changes). Shouldn't biblatex do at least a \protected@edef here? Then one could simply reset the translation after the first use and all this measuring wouldn't be needed. – Ulrike Fischer Apr 22 '21 at 12:02
  • @ moewe, If you happen to have any comments on the approaches I posted in the other answer, they'd be much welcome. (I'm currently using the fourth). – gusbrs Aug 16 '23 at 15:35
0

I'm revisiting this problem, and I've never been completely happy with the alternatives. But, that given, perhaps this is the best place to keep track of them. So, I leave below possible working alternatives, each with their caveats.

First, testing the content of postnote:

\documentclass{article}

\usepackage[style=authoryear,autocite=footnote]{biblatex}

\addbibresource{biblatex-examples.bib}

\newcounter{mytranslationcount}[section] \NewDocumentCommand{\mytranslation}{}{% \ifnumequal{\value{mytranslationcount}}{0} {% \addcomma\addspace\printtext{my translation, as will be all following ones in a foreign language}% \stepcounter{mytranslationcount}% } {}% } \AtEveryCitekey{% \ifboolexpr { ( ( test { \iffieldequalstr{postnote}{\mytranslation} } or test { \iffieldequalstr{postnote}{\mytranslation{}} } ) and not test {\ifnumequal{\value{mytranslationcount}}{0}} ) } {\clearfield{postnote}} {}% }

\begin{document}

\section{Section 1}

% Looks good. \autocite[\pnfmt{3333-3345}\mytranslation{}]{sigfridsson}

% Also good. \autocite[\pnfmt{3333-3345}\mytranslation]{sigfridsson}

\section{Section 2}

% Still good. \autocite[\mytranslation{}]{sigfridsson}

% OK. \autocite[\mytranslation{}]{sigfridsson}

\end{document}

Second, making \mytranslation a flag (to be used outside of the citation):

\documentclass{article}

\usepackage[style=authoryear,autocite=footnote]{biblatex}

\addbibresource{biblatex-examples.bib}

\newcounter{mytranslationcount}[section] \newboolean{mytranslationbool} \usepackage{xpatch} \newcommand*{\mytranslation}{% \AtNextCitekey{\booltrue{mytranslationbool}}} \xapptobibmacro{postnote}{% \ifboolexpr{ ( test {\ifbool{mytranslationbool}} and test {\ifnumequal{\value{mytranslationcount}}{0}} ) } {\addcomma\addspace\printtext{my translation, as will be all following ones in a foreign language}% \stepcounter{mytranslationcount}} {}}{}{}

\begin{document}

\section{Section 1}

% Looks good. \mytranslation\autocite[\pnfmt{3333-3345}]{sigfridsson}

% Also good. \mytranslation\autocite[\pnfmt{3333-3345}]{sigfridsson}

\section{Section 2}

% Still good. \mytranslation\autocite{sigfridsson}

% OK. \mytranslation\autocite{sigfridsson}

\end{document}

Third, a variant using the measuring technique from @moewe's answer:

\documentclass{article}

\usepackage[style=authoryear,autocite=footnote]{biblatex}

\addbibresource{biblatex-examples.bib}

\newcounter{mytranslationcount}[section] \newboolean{mytranslationbool} \newrobustcmd*{\mytranslation}{% \ifnumequal{\value{mytranslationcount}}{0}{% \addcomma\addspace\printtext{my translation, as will be all following ones in a foreign language}% \ifbool{mytranslationbool}{\stepcounter{mytranslationcount}}{}}{}}

% Width measurement technique by egreg at: % https://tex.stackexchange.com/a/53091 \def\foreverunspace{% \ifnum\lastnodetype=11 \unskip\foreverunspace \else \ifnum\lastnodetype=12 \unkern\foreverunspace \else \ifnum\lastnodetype=13 \unpenalty\foreverunspace \fi \fi \fi }

% Clear the postnote field if its width is zero. But the measurement expands % it, thus we have to protect the counter step in '\mytranslation' behind % 'mytranslationbool', which we toggle true after measuring. \AtEveryCitekey{% \iffieldundef{postnote}{}{% \setbox0=\hbox{\thefield{postnote}\foreverunspace}% \ifdim\wd0=0pt \clearfield{postnote} \fi \booltrue{mytranslationbool}% }% }

\begin{document}

\section{Section 1}

% Looks good. \autocite[\pnfmt{3333-3345}\mytranslation{}]{sigfridsson}

% Also good. \autocite[\pnfmt{3333-3345}\mytranslation]{sigfridsson}

\section{Section 2}

% Still good. \autocite[\mytranslation{}]{sigfridsson}

% OK. \autocite[\mytranslation{}]{sigfridsson}

\end{document}

And a fourth:

\documentclass{article}

\usepackage[style=authoryear,autocite=footnote]{biblatex}

\addbibresource{biblatex-examples.bib}

\ExplSyntaxOn \newcounter{mytranslationcount}[section] \NewDocumentCommand{\mytranslation}{} { \int_compare:nNnT { \value { mytranslationcount } } = { 0 } { \addcomma\addspace\printtext{my~translation,~as~will~be~all~ following~ones~in~a~foreign~language} \stepcounter{mytranslationcount} } } \AtEveryCitekey { \tl_set:Nn \l_tmpa_tl { \mytranslation } \tl_set:Nn \l_tmpb_tl { \mytranslation{} } \iffieldundef { postnote } { } { \bool_lazy_and:nnT { \int_compare_p:nNn { \value { mytranslationcount } } > { 0 } } { \tl_if_eq_p:Nc \l_tmpa_tl { abx@field@postnote } || \tl_if_eq_p:Nc \l_tmpb_tl { abx@field@postnote } } { \clearfield { postnote } } } } \ExplSyntaxOff

\begin{document}

\section{Section 1}

% Looks good. \autocite[\pnfmt{3333-3345}\mytranslation{}]{sigfridsson}

% Also good. \autocite[\pnfmt{3333-3345}\mytranslation]{sigfridsson}

\section{Section 2}

% Still good. \autocite[\mytranslation{}]{sigfridsson}

% OK. \autocite[\mytranslation]{sigfridsson}

\end{document}

gusbrs
  • 13,740