10

I would like to extend the entry type set of biblatex to take additional fields - but I didn't get it to work so far.

Consider this: You have a series of books all with the same author(s) and main title, each volume has a separate title and they should be cited in a set-like manner (like 1a,1b,...). Standard biblatex / biber setup results in

enter image description here

which is not the desired result.

So adding a new field like mastertitle with

\DeclareDatamodelFields[type=field, datatype=literal]{mastertitle}
\DeclareDatamodelEntryfields{mastertitle}

and

\DeclareSourcemap{
  \maps[datatype=bibtex]{
    \map{
      \pertype{set}
      \step[fieldsource=maintitle]
    }
    \map{
      \step[fieldsource=mastertitle]
    }
  }
}

is no big deal - except that it does not show up in bibers output of sets:

\entry{A}{set}{}
  \set{A1,A2,A3}
  \field{sortinit}{G}
  \field{sortinithash}{5e8d2bf9d38de41b1528bd307546008f}
\endentry

I would like to drop author and maintitle for each set entry and put it in front of the whole list via definition in the bib file to get something like this:

enter image description here

So here are my basic questions:

  1. How do I get additional info to the bibers output of set entries?
  2. How do I remove author and title in set entries but not in other books?
  3. How can I sort the subentries by volume?

Thx...

MWE:

\documentclass{article}
\usepackage[ansinew]{inputenc}
\usepackage[ngerman]{babel}
\usepackage[babel,german=quotes]{csquotes}
% ... with hq fonts...
\usepackage[T1]{fontenc}
\usepackage{lmodern}

\begin{filecontents*}{\jobname.bib}
@Set{A,
  entryset    = {A1,A2,A3},
  author      = {René Goscinny and Albert Uderzo},
  title       = {Asterix und Obelix},
  maintitle   = {Asterix und Obelix},
  mastertitle = {-0-},
}

@Book{A1,
  author      = {René Goscinny and Albert Uderzo},
  maintitle   = {Asterix und Obelix},
  title       = {Asterix der Gallier},
  year        = {1959},
  volume      = {1},
  isbn        = {9783770400010},
}

@Book{A2,
  author      = {René Goscinny and Albert Uderzo},
  maintitle   = {Asterix und Obelix},
  title       = {Asterix und Kleopatra},
  year        = {1968},
  volume      = {2},
  isbn        = {9783770400027},
}

@Book{A3,
  author      = {René Goscinny and Albert Uderzo},
  maintitle   = {Asterix und Obelix},
  title       = {Asterix als Gladiator},
  year        = {1969},
  volume      = {3},
  isbn        = {9783770436033},
}

@Book{A4,
  author      = {René Goscinny and Albert Uderzo},
  maintitle   = {Asterix und Obelix},
  title       = {Der Kampf der Häuptlinge},
  year        = {1969},
  volume      = {4},
  isbn        = {9783770436040},
}
\end{filecontents*}
% write dbx
\begin{filecontents*}{xset.dbx}
  \DeclareDatamodelFields[type=field, datatype=literal]{mastertitle}
  \DeclareDatamodelEntryfields{mastertitle}
\end{filecontents*}
% load biblatex

\usepackage[doi=false,defernumbers=true,backend=biber,subentry,sorting=nyt,sortsets=true,style=numeric-comp,datamodel=xset]{biblatex}
\usepackage{xpatch}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% add maintitle to set type
\DeclareSourcemap{
  \maps[datatype=bibtex]{
    \map{
      \pertype{set}
      \step[fieldsource=maintitle]
    }
    \map{
      \step[fieldsource=mastertitle]
    }
  }
}
% newline per set entry
\renewcommand{\entrysetpunct}{\par\nobreak}
% patch entryset
\xpatchbibdriver{set}
  {\entryset}
  {\iffieldundef{author}{}{\printfield{author}}%
   \iffieldundef{maintitle}{\iffieldundef{mastertitle}{}{\printfield{mastertitle}\par}}{\printfield{maintitle}\par}\entryset}
  {}{\errmessage{failed to add maintitle to set driver}}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% bib data
\addbibresource{\jobname.bib}
%
\begin{document}
Asterix\supercite{A1,A2} und Obelix\supercite{A3,A4} und Idefix\supercite{A}.
\printbibliography
\end{document}
  • 2
    Interesting question. Apart from necessary sorting information @set usually does not contain any data of its own, it is simply a 'container' for its set members. Theoretically it should be possible to add more information, but you would probably have to rewrite the entire @set system to make use of it. For the output you would like to see you may want to have a look at the knuth:ct works in biblatex-examples.bib ... – moewe Nov 13 '18 at 11:03
  • 2
    the citations will not come out as desired, but everything else should more or less look as desired. Sorting should be taken care of with sorttitle, just write sorttitle = {Asterix und Obelix 01},, sorttitle = {Asterix und Obelix 02}, etc. Alternatively if you want you can also add maintitle and volume to a custom sorting template. – moewe Nov 13 '18 at 11:05
  • @moewe - thanks for the tips; I'll check knuth:ch, Meanwhile I found link and link which got me to something like \iffieldundef{author}{\citeauthor{A1}}{\printfield{author}}\par as a basic sketch to patch the set driver in the MWE. Can I get the citekey of the set within itself (to replace A1 with something like \citekey{me}1) somehow? – AndiW Nov 13 '18 at 11:34
  • one step closer: with \makeatletter \def\thisentrykey{\abx@field@entrykey} \def\subkeydelim{1} \makeatother´ and \citefield{\thisentrykey\subkeydelim}{<fieldname>}` one can read field content to the enveloping @set... (use \subkeydel to define the difference between the @set-key and the naming scheme of it's entries...) – AndiW Nov 13 '18 at 12:14
  • 1
    I think most bits of the @set system are hard-coded in Biber (case in point: github.com/plk/biber/blob/…). The @set system needs more support than just normal field processing from the backend: Usually new entries need to be processed as a result of entryset and that needs special handling. – moewe Dec 2 '18 at 14:25
2

Following my suggestion to use the related feature in biblatex: why does @mvbook not work as a containter (like @set)?, here is a solution that tries to implement the thing missing there, namely set citations of the form "[1a]" instead of \volcite's "[1, Bd. 1]".

As mentioned in the other answer, some of the @set stuff is implemented (hard-coded) on the Biber side, but it is sort of possible to come up with biblatex-side emulations for many things.

\documentclass{article}
\usepackage[T1]{fontenc}
\usepackage[utf8]{inputenc}
\usepackage[ngerman]{babel}
\usepackage[babel,german=quotes]{csquotes}
\usepackage[doi=false,backend=biber,subentry,sorting=nyt,style=numeric-comp,sortsets=true]{biblatex}
\usepackage{hyperref}

\makeatletter
\newcommand*{\begrelateddelimmultivolumeset}{\newunitpunct\par\nobreak}
\newbibmacro*{related:multivolumeset}[1]{%
  \entrydata*{#1}{%
    \printfield[bibentrysetcount]{entrysetcount}%
    \blx@anchor % this is to get the link right, everything else is
                % a straightforward extension of related:multivolume
    \setunit{\addspace}%
    \printtext{%
      \printfield{volume}%
      \printfield{part}}%
    \setunit*{\addcolon\space}%
    \usebibmacro{title}%
    \ifboolexpr{
      test {\ifnamesequal{author}{savedauthor}}
      or
      test {\ifnameundef{author}}
    }
      {}
      {\usebibmacro{bytypestrg}{author}{author}%
       \setunit{\addspace}%
       \printnames[byauthor]{author}
       \newunit\newblock}%
    \ifboolexpr{
      test {\ifnamesequal{editor}{savededitor}}
      or
      test {\ifnameundef{editor}}
    }
      {}
      {\usebibmacro{byeditor+others}%
       \newunit\newblock}%
    \ifboolexpr{
      test {\iflistsequal{location}{savedlocation}}
      or
      test {\iflistundef{location}}
    }
      {}
      {\printlist{location}}%<- typo
    \ifboolexpr{
      test {\iflistsequal{publisher}{savedpublisher}}
      or
      test {\iflistundef{publisher}}
    }
      {\setunit*{\addcomma\space}}
      {\setunit*{\addcolon\space}%
       \printlist{publisher}%
       \setunit*{\addcomma\space}}%
  \printdate%
  \newunit\newblock
  \ifboolexpr{
    test {\iffieldsequal{isbn}{savedisbn}}
    or
    not togl {bbx:isbn}
  }
    {}
    {\printfield{isbn}}}}

% {<entrykey>}
% get the hash-keys of all related 'children'
\newcommand*{\blx@fakeset@gethashkey}[1]{%
  \listcsxadd{blx@fakeset@hashkeys@toprocess@\the\c@refsection}{%
    \detokenize{#1}}%
  % save fakeset parent of hash-key
  \csxdef{blx@fakeset@setp@\the\c@refsection @#1}{\strfield{entrykey}}%
  \ifcsundef{blx@fakeset@plist@\the\c@refsection}
    {\global\cslet{blx@fakeset@plist@\the\c@refsection}\@empty}
    {}%
  \xifinlistcs{\strfield{entrykey}}{blx@fakeset@plist@\the\c@refsection}
    {}
    {\listcsxadd{blx@fakeset@plist@\the\c@refsection}{\strfield{entrykey}}}%
  \advance\blx@tempcnta\@ne
  \csxdef{blx@fakeset@seti@\the\c@refsection @#1}{\the\blx@tempcnta}%
}

% {<cmd>}{<csv field>}
% execute <cmd> for each item in the csv field
% there should be a way to avoid the \def here ...
\newrobustcmd*{\blx@fakeset@forcsvfield}[2]{%
  \def\blx@fakeset@forcsvfield@handler{#1}
  \expandafter\expandafter\expandafter\expandafter\expandafter\expandafter
  \expandafter\expandafter\expandafter\expandafter\expandafter\expandafter
  \expandafter\expandafter\expandafter\expandafter\expandafter\expandafter
  \expandafter\expandafter\expandafter\expandafter\expandafter\expandafter
  \expandafter\expandafter\expandafter\expandafter\expandafter\expandafter
  \expandafter
  \forcsvlist
  \expandafter\expandafter\expandafter\expandafter\expandafter\expandafter
  \expandafter\expandafter\expandafter\expandafter\expandafter\expandafter
  \expandafter\expandafter\expandafter\expandafter\expandafter\expandafter
  \expandafter\expandafter\expandafter\expandafter\expandafter\expandafter
  \expandafter\expandafter\expandafter\expandafter\expandafter\expandafter
  \expandafter
  \blx@fakeset@forcsvfield@handler
  \expandafter\expandafter\expandafter\expandafter\expandafter\expandafter
  \expandafter\expandafter\expandafter\expandafter\expandafter\expandafter
  \expandafter\expandafter\expandafter\expandafter\expandafter\expandafter
  \expandafter\expandafter\expandafter\expandafter\expandafter\expandafter
  \expandafter\expandafter\expandafter\expandafter\expandafter\expandafter
  \expandafter
  {\thefield{#2}}}

\AtDataInput{%
  \blx@tempcnta\z@
  \iffieldequalstr{relatedtype}{multivolumeset}
    {\iffieldundef{related}
       {}
       {\blx@fakeset@forcsvfield{\blx@fakeset@gethashkey}{related}}}
    {}%
  % sorted list of *all* entries in the .bbl including cloned entries
  \listcsxadd{blx@fakeset@dlist@aentry@\the\c@refsection
    @\blx@dlist@name}{\strfield{entrykey}}%
}

% resolve hash-key to bib-key and other usable stuff
% {<hash-key>}
\newcommand*{\blx@fakeset@resolvekeys}[1]{%
  % get the entrykey from the bib, not the cloned hash
  \entrydata{#1}{\xdef\blx@fakeset@temp@bibkey{\strfield{clonesourcekey}}}%
  % map hash-key to bib-key
  \csxdef{blx@fakeset@bibkey@\the\c@refsection @#1}{\blx@fakeset@temp@bibkey}%
  % map fakeset parent to list of its children
  \ifcsundef{blx@fakeset@setc@\the\c@refsection @%
             \csuse{blx@fakeset@setp@\the\c@refsection @#1}}
    {\global\cslet{blx@fakeset@setc@\the\c@refsection
                   @\csuse{blx@fakeset@setp@\the\c@refsection @#1}}
       \@empty
     \blx@tempcnta\z@}
    {}%
  \advance\blx@tempcnta\@ne
  \listcsxadd{blx@fakeset@setc@\the\c@refsection
              @\csuse{blx@fakeset@setp@\the\c@refsection @#1}}
    {\blx@fakeset@temp@bibkey}%
  % map bib-key to fakeset parent
  \csxdef{blx@setc@\the\c@refsection @\blx@fakeset@temp@bibkey}{%
    \csuse{blx@fakeset@setp@\the\c@refsection @#1}}%
  % copy data from hash-key to bib-key
  \global\csletcs{blx@data@\the\c@refsection @\blx@refcontext@context
                  @\blx@fakeset@temp@bibkey}%
    {blx@data@\the\c@refsection @\blx@refcontext@context @#1}%
  % fakeset count
  \iftoggle{blx@sortsets}
    {\csnumgdef{blx@fakeset@seti@\the\c@refsection @#1}{\blx@tempcnta}}
    {}%
  \global\csletcs{blx@seti@\the\c@refsection @\blx@fakeset@temp@bibkey}
    {blx@fakeset@seti@\the\c@refsection @#1}%
  \global\csletcs{blx@seti@\the\c@refsection @#1}
    {blx@fakeset@seti@\the\c@refsection @#1}%
}

\newcommand*{\blxfset@addrealkey}[2]{%
  \listgadd{#1}{#2}%
  \ifcsundef{blx@fakeset@setc@\the\c@refsection @#2}
    {}
    {\forlistcsloop{\listadd{#1}}{blx@fakeset@setc@\the\c@refsection @#2}}}

% {<list_1>}{<list_2>}{<item>}
% worker macro to sort:
% add <item> to <list_2> if it exists in <list_1>
\newcommand*{\blx@fakeset@sortbycsinto@i}[3]{%
  \xifinlist{\detokenize{#3}}{#1}
    {\listeadd{#2}{\detokenize{#3}}}
    {}}

% {<list_1>}{<cs list_2>}{<list_3>}
% sorts all entries in <list_2> in the order given by <cs list_2> into <list_3>
\newcommand*{\blx@fakeset@sortbycsinto}[3]{%
  \forlistcsloop{\blx@fakeset@sortbycsinto@i{#1}{#3}}{#2}}

\newcommand*{\blx@fakeset@csvstrtolist@i}[2]{%
  \listeadd{#1}{\detokenize{#2}}}

\newcommand*{\blx@fakeset@listtocsvstr@i}[2]{%
  \appto{#1}{#2,}}

\newcommand*{\blx@fakeset@listtocsvstr}[2]{%
  \forlistloop{\blx@fakeset@listtocsvstr@i{#1}}{#2}}

\newcommand*{\blx@fakeset@sortrelatedfield}[1]{%
  \begingroup
  \blx@getdata{#1}%
  \let\blx@fakeset@tempa\@empty
  \let\blx@fakeset@tempb\@empty
  \blx@fakeset@forcsvfield
    {\blx@fakeset@csvstrtolist@i{\blx@fakeset@tempa}}
    {related}%
  \blx@fakeset@sortbycsinto
    {\blx@fakeset@tempa}
    {blx@fakeset@dlist@aentry@\the\c@refsection @\blx@refcontext@context}
    {\blx@fakeset@tempb}
  \let\blx@fakeset@tempa\@empty
  \blx@fakeset@listtocsvstr{\blx@fakeset@tempa}{\blx@fakeset@tempb}%
  \blx@bbl@addentryfield{#1}{\the\c@refsection}{related}%
    {\blx@refcontext@context}{\blx@fakeset@tempa}%
  \endgroup
}

% we can only resolve our fakeset entries once all entries have been read
\preto{\blx@bbl@endrefsection}{%
  \letcs\blx@fakeset@tempa{blx@fakeset@hashkeys@toprocess@\the\c@refsection}%
  \ifundef\blx@fakeset@tempa
    {\let\blx@fakeset@tempa\@empty}
    {}%
  \iftoggle{blx@sortsets}
    {\letcs\blx@fakeset@tempb{blx@fakeset@dlist@aentry@\the\c@refsection
       @\blx@refcontext@context}%
     \blx@filtercitesort\blx@fakeset@tempb{blx@fakeset@tempa}%
     \let\blx@fakeset@tempa\blx@fakeset@tempb}
    {}%
  \forlistloop{\blx@fakeset@resolvekeys}\blx@fakeset@tempa
  \let\blx@fakeset@tempa\@empty
  \forlistcsloop
    {\blxfset@addrealkey{\blx@fakeset@tempa}}
    {blx@dlist@centry@\the\c@refsection @\blx@refcontext@context}%
  \global\cslet{blx@dlist@centry@\the\c@refsection @\blx@refcontext@context}%
    \blx@fakeset@tempa
  \iftoggle{blx@sortsets}
    {\ifcsundef{blx@fakeset@plist@\the\c@refsection}
       {}
       {\forlistcsloop
          {\blx@fakeset@sortrelatedfield}
          {blx@fakeset@plist@\the\c@refsection}}}
    {}%
}%
\makeatother

\usepackage{filecontents}
\begin{filecontents}{\jobname.bib}
@mvbook{A,
  author      = {René Goscinny and Albert Uderzo},
  sorttitle   = {Asterix und Obelix 00},
  sortyear    = {1959},
  maintitle   = {Asterix und Obelix},
  location    = {Paris},
  related     = {A:V2,A:V5,A:V3,A:V1,A:V4},
  relatedtype = {multivolumeset},
}
@Book{A:V1,
  crossref    = {A},
  volume      = {1},
  title       = {Asterix der Gallier},
  sorttitle   = {Asterix und Obelix 01},
  year        = {1959},
  publisher   = {Dargaud},
  options     = {dataonly},
}
@Book{A:V2,
  crossref    = {A},
  title       = {Asterix und Kleopatra},
  volume      = {2},
  sorttitle   = {Asterix und Obelix 02},
  year        = {1968},
  publisher   = {Dargaud},
  options     = {dataonly},
}
@Book{A:V3,
  crossref    = {A},
  title       = {Asterix als Gladiator},
  volume      = {3},
  sorttitle   = {Asterix und Obelix 03},
  year        = {1969},
  publisher   = {Dargaud},
  options     = {dataonly},
}
@Book{A:V4,
  crossref    = {A},
  title       = {Der Kampf der Häuptlinge},
  volume      = {4},
  sorttitle  = {Asterix und Obelix 04},
  year        = {1969},
  publisher   = {Dargaud},
  options     = {dataonly},
}
@Book{A:V5,
  crossref    = {A},
  title       = {Die goldene Sichel},
  volume      = {5},
  sorttitle   = {Asterix und Obelix 05},
  year        = {1970},
  publisher   = {Ehapa-Verlag},
  options     = {dataonly},
}
@Book{A:VX,
  crossref    = {A},
  title       = {Tour de France},
  volume      = {6},
  sorttitle   = {Asterix und Obelix 06},
  year        = {1970},
  publisher   = {Ehapa-Verlag},
  location    = {Berlin},
}
\end{filecontents}

\addbibresource[label=asterix]{\jobname.bib}
\addbibresource[label=blxex]{biblatex-examples.bib}

\begin{document}
\volcite{1}{A}

\cite{A:V1}

\cite{A:V4}

\cite{A:V1,A:V4}

\cite{A,A:VX}
\printbibliography

\clearpage
\section{Don't cite the other one}
\newrefsection[asterix]
\cite{A:V1}

\cite{A:V4}

\cite{A:V1,A:V4}

\cite{A}
\printbibliography

\clearpage
\section{Nocite}
\newrefsection[asterix]
\nocite{*}

\cite{A:V1}

\cite{A:V4}

\cite{A:V1,A:V4}

\cite{A}
\printbibliography

\clearpage
\section{sigfridsson}
\newrefsection[asterix,blxex]
\cite{sigfridsson}
\printbibliography

\clearpage
\section{sigfridsson with set}
\newrefsection[asterix,blxex]
\cite{sigfridsson,A:V4,A}
\printbibliography
\end{document}

Citations "[1, Bd. 1] [1a] [1d] [1a, 1d] [1, 2]". The bibliography shows as desired, i.e. with a common section for the <code>@mvbook</code> bit and new lines for the separate bits.

  • pretty cool, man... I've been working on manually launching a driver for the head part after collecting data from the children - far too much as you demonstrated. Impressing. Still working on 2 minor things: aligning the "Bd." in the last picture works with \DeclareFieldFormat{bibentrysetcount}{\makebox[1.25em][l]{\smash{\mknumalph{#1})}}} but only if you set the whole bibliography in a sloppypar, the 2nd one (see first pic in the question) I didn't get yet is to make the children itemize-like (indent 2nd+ lines). I tried bibmacro begrelated/endrelated but... u helped alot – AndiW Dec 3 '18 at 18:17
4

Thanks to moewe in his first comment I tried it again digging through biblatex.sty and standard.bbx.

Here is the solution:

1) biber assigns the command \inset{<setkey>} to every enty that's part of the set. biblatex uses this to define the field \abx@field@entrykey which is made available with:

\makeatletter
\def\thisentrykey{\abx@field@entrykey}
\makeatother

2) Since \abx@field@entrykey holds the citekey of the @set we can read the fields from the subentries (if the key naming convention is consistent) and use them in the @set driver:

\xpatchbibdriver{set}
  {\entryset} % https://tex.stackexchange.com/a/387849/118739
  {\citeauthorfirstlast{\thisentrykey\subkeydelim}. \textit{\citefield{\thisentrykey\subkeydelim}{maintitle}.}\par\entryset}
  {}{\errmessage{failed to add maintitle to set driver}}

3) Redefine the bibliography driver for book to print name and titles only if the entry is not part of a @set:

\DeclareBibliographyDriver{book}{%
  \usebibmacro{bibindex}%
  \usebibmacro{begentry}%
    \iffieldundef{entrysetcount}% modification start
      {%\errmessage{book not in set}
        \usebibmacro{author/editor+others/translator+others}%
     \setunit{\printdelim{nametitledelim}}\newblock
     \usebibmacro{maintitle+title}}
      {%\errmessage{book only in set}
        \iffieldundef{volume}{}{\printfield{volume}}
     \usebibmacro{title}}%      moodification end
  \newunit
  \printlist{language}%
  [...]

Result:

enter image description here

Adapting this to other bibliography drivers should be no big deal...

And here's the complete code:

\documentclass{article}
\usepackage[ansinew]{inputenc}
\usepackage[ngerman]{babel}
\usepackage[babel,german=quotes]{csquotes}
% ... with hq fonts...
\usepackage[T1]{fontenc}
\usepackage{lmodern}

\begin{filecontents*}{\jobname.bib}
@Set{A,
  entryset    = {A:V1,A:V2,A:V3},
  author      = {René Goscinny and Albert Uderzo},
  maintitle   = {Asterix der Gallier},
}

@Book{A:V1,
  author      = {René Goscinny and Albert Uderzo},
  maintitle   = {Asterix und Obelix},
  title       = {Asterix der Gallier},
  sorttitle   = {Asterix und Obelix 01},
  year        = {1959},
  volume      = {1},
  isbn        = {9783770400010},
}

@Book{A:V2,
  author      = {René Goscinny and Albert Uderzo},
  maintitle   = {Asterix und Obelix},
  title       = {Asterix und Kleopatra},
  sorttitle   = {Asterix und Obelix 02},
  year        = {1968},
  volume      = {2},
  isbn        = {9783770400027},
}

@Book{A:V3,
  author      = {René Goscinny and Albert Uderzo},
  maintitle   = {Asterix und Obelix},
  title       = {Asterix als Gladiator},
  sorttitle   = {Asterix und Obelix 03},
  year        = {1969},
  volume      = {3},
  isbn        = {9783770436033},
}

@Book{A:V4,
  author      = {René Goscinny and Albert Uderzo},
  maintitle   = {Asterix und Obelix},
  title       = {Der Kampf der Häuptlinge},
  year        = {1969},
  volume      = {4},
  isbn        = {9783770436040},
}

@Set{B,
  entryset    = {B:V1,B:V2,B:V3},
  author      = {Max Muster und Theresa Test},
  maintitle   = {Der Gallier Asterix},
}

@Book{B:V1,
  author      = {Max Muster und Theresa Test},
  maintitle   = {Der Gallier Asterix},
  title       = {Asterix der Gallier},
  sorttitle   = {Asterix und Obelix 01},
  year        = {1989},
  volume      = {1},
  isbn        = {9783770400010},
}

@Book{B:V2,
  author      = {Max Muster und Theresa Test},
  maintitle   = {Der Gallier Asterix},
  title       = {Asterix und Kleopatra},
  sorttitle   = {Asterix und Obelix 02},
  year        = {1998},
  volume      = {2},
  isbn        = {9783770400027},
}

@Book{B:V3,
  author      = {Max Muster und Theresa Test},
  maintitle   = {Der Gallier Asterix},
  title       = {Asterix als Gladiator},
  sorttitle   = {Asterix und Obelix 03},
  year        = {1999},
  volume      = {3},
  isbn        = {9783770436033},
}

@Book{B:V4,
  author      = {Max Muster und Theresa Test},
  maintitle   = {Der Gallier Asterix},
  title       = {Der Kampf der Häuptlinge},
  year        = {1999},
  volume      = {4},
  isbn        = {9783770436040},
}
\end{filecontents*}
% load biblatex
\usepackage[doi=false,defernumbers=true,backend=biber,subentry,sorting=nyt,sortsets=true,style=numeric-comp]{biblatex}
\usepackage{xpatch}
%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\DeclareBibliographyDriver{book}{%
  \usebibmacro{bibindex}%
  \usebibmacro{begentry}%
    \iffieldundef{entrysetcount}% modification start
      {%\errmessage{book not in set}
        \usebibmacro{author/editor+others/translator+others}%
     \setunit{\printdelim{nametitledelim}}\newblock
     \usebibmacro{maintitle+title}}
      {%\errmessage{book only in set}
        \iffieldundef{volume}{}{\printfield{volume}}
        \usebibmacro{title}}%      moodification end
  \newunit
  \printlist{language}%
  \newunit\newblock
  \usebibmacro{byauthor}%
  \newunit\newblock
  \usebibmacro{byeditor+others}%
  \newunit\newblock
  \printfield{edition}%
  \newunit
  \iffieldundef{maintitle}
    {\printfield{volume}%
     \printfield{part}}
    {}%
  \newunit
  \printfield{volumes}%
  \newunit\newblock
  \usebibmacro{series+number}%
  \newunit\newblock
  \printfield{note}%
  \newunit\newblock
  \usebibmacro{publisher+location+date}%
  \newunit\newblock
  \usebibmacro{chapter+pages}%
  \newunit
  \printfield{pagetotal}%
  \newunit\newblock
  \iftoggle{bbx:isbn}
    {\printfield{isbn}}
    {}%
  \newunit\newblock
  \usebibmacro{doi+eprint+url}%
  \newunit\newblock
  \usebibmacro{addendum+pubstate}%
  \setunit{\bibpagerefpunct}\newblock
  \usebibmacro{pageref}%
  \newunit\newblock
    \iftoggle{bbx:related}
    {\usebibmacro{related:init}%
     \usebibmacro{related}}
    {}%
  \usebibmacro{finentry}}
%
% newline per set entry
\renewcommand{\entrysetpunct}{\par\nobreak}
%
% subentry naming convention
\def\subkeydelim{:V1}
%
% https://tex.stackexchange.com/a/73819/118739
\DeclareCiteCommand{\citeauthorfirstlast}
  {\boolfalse{citetracker}%
   \boolfalse{pagetracker}%
   \DeclareNameAlias{labelname}{first-last}%
   \usebibmacro{prenote}}
  {\ifciteindex
     {\indexnames{labelname}}
     {}%
   \printnames{labelname}}
  {\multicitedelim}
  {\usebibmacro{postnote}}
%
% access to own key and key delimiter
\makeatletter
\def\thisentrykey{\abx@field@entrykey}
\makeatother
%
% patch entryset
\xpatchbibdriver{set}
  {\entryset} % https://tex.stackexchange.com/a/387849/118739
  {\citeauthorfirstlast{\thisentrykey\subkeydelim}. \textit{\citefield{\thisentrykey\subkeydelim}{maintitle}.}\par\entryset}
  {}{\errmessage{failed to add maintitle to set driver}}
\DeclareFieldFormat{bibentrysetcount}{\makebox[15pt][l]{\mknumalph{#1})}}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% bib data
\addbibresource{\jobname.bib}
%
\begin{document}
Asterix\supercite{A:V1,A:V2} und Obelix\supercite{A:V3,A:V4} und Idefix\supercite{A}.

Asterix\supercite{B:V1,B:V2} und Obelix\supercite{B:V3,B:V4} und Idefix\supercite{B}.
\printbibliography
\end{document}
  • AndiW, Very nice! – gusbrs Nov 13 '18 at 16:03
2

Once more my own answer to my own question since I think it's an improvement and has quite some potential...

After playing around a bit I came up with this solution that reads data from the first entryset id, adds missing data from other set members and does some integrity checks. After collecting data (what's collected actually is up to the user) it prints the following bibliography:

enter image description here

I must admit it looks pretty the same compared with the first answer - but changing just one line of code (line 291) from

\DeclareXsetOptions{ ontop = {url} }

to

\DeclareXsetOptions{ nottop = {maintitle} }

results in

enter image description here

compared to biblatex' default output which is

enter image description here

In the MWE the set driver is patched to represent the common data in biblatex books; a useful expansion would be to decide what to print depending on what types of entries are combined within the set (\strfield{entrytype}). Here's the code:

\documentclass{article}
\usepackage[ansinew]{inputenc}
\usepackage[ngerman]{babel}
\usepackage[babel,german=quotes]{csquotes}
\usepackage[doi=false,defernumbers=true,backend=biber,subentry,sorting=nyt,sortsets=true,style=numeric-comp,datamodel=xset]{biblatex}
\usepackage{xpatch}

\newlinechar=`\^^J

\makeatletter
%%% test bib file %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\begin{filecontents}{\jobname.bib}
@Set{A,
  entryset    = {A:V1,A:V2,A:V3,A:V4,A:V5},
}

@Book{A:V1,
  author      = {René Goscinny and Albert Uderzo},  title       = {Asterix der Gallier},
  maintitle   = {Asterix und Obelix},               sorttitle   = {Asterix und Obelix 01},
  year        = {1959},                             isbn        = {1234567890123},
  publisher   = {Dargaud},                          location    = {Paris},
  volume      = {1},
}

@Book{A:V2,
  author      = {René Goscinny and Albert Uderzo},  title       = {Asterix und Kleopatra},
  maintitle   = {Asterix und Obelix},               sorttitle   = {Asterix und Obelix 02},
  year        = {1968},                             isbn        = {2345678901234},
  publisher   = {Dargaud},                          location    = {Paris},
  volume      = {2},
}

@Book{A:V3,
  author      = {René Goscinny and Albert Uderzo},  title       = {Asterix als Gladiator},
  maintitle   = {Asterix und Obelix},               sorttitle   = {Asterix und Obelix 03},
  year        = {1969},                             isbn        = {3456789012345},
  publisher   = {Dargaud},                          location    = {Paris},
  volume      = {3},
}

@Book{A:V4,
  author      = {René Goscinny and Albert Uderzo},  title       = {Der Kampf der Häuptlinge},
  maintitle   = {Asterix und Obelix},               sorttitle   = {Asterix und Obelix 04},
  year        = {1969},                             isbn        = {4567890123456},
  publisher   = {Dargaud},                          location    = {Paris},
  volume      = {4},
}

@Book{A:V5,
  author      = {René Goscinny and Albert Uderzo},  title       = {Die goldene Sichel},
  maintitle   = {Asterix und Obelix},               sorttitle   = {Asterix und Obelix 05},
  year        = {1970},                             isbn        = {5678901234567},
  publisher   = {Ehapa-Verlag},                     location    = {Berlin},
  volume      = {5},
}

@Book{A:VX,
  author      = {René Goscinny and Albert Uderzo},  title       = {Tour de France},
  maintitle   = {Asterix und Obelix},               sorttitle   = {Asterix und Obelix 06},
  year        = {1970},                             isbn        = {6789012345678},
  publisher   = {Ehapa-Verlag},                     location    = {Berlin},
  volume      = {6},
}

@Book{DHS:nik,
  editor    = {{Deutsche Hauptstelle für Suchtfragen e.~V.}},
  title     = {Tabakabhängigkeit},
  year      = {2017},
  url       = {www.dhs.de},
  urldate   = {2018-04-30},
  isbn      = {978-3-937587-00-4},
  maintitle = {Suchtmedizinische Reihe},
  sorttitle = {Suchtmedizinische Reihe 01},
  volume    = {1},
}

@Book{DHS:alk,
  editor    = {{Deutsche Hauptstelle für Suchtfragen e.~V.}},
  title     = {Alkoholabhängigkeit},
  year      = {2017},
  url       = {www.dhs.de},
  urldate   = {2018-04-30},
  isbn      = {978-3-937587-01-1},
  maintitle = {Suchtmedizinische Reihe},
  sorttitle = {Suchtmedizinische Reihe 02},
  volume    = {2},
}

@Book{DHS:drogen,
  editor    = {{Deutsche Hauptstelle für Suchtfragen e.~V.}},
  title     = {Drogenabhängigkeit},
  year      = {2017},
  url       = {www.dhs.de},
  urldate   = {2018-11-11},
  isbn      = {978-3-937587-03-5},
  maintitle = {Suchtmedizinische Reihe},
  sorttitle = {Suchtmedizinische Reihe 04},
  volume    = {4},
}

@Set{DHS:SMR,
  entryset  = {DHS:alk,DHS:nik,DHS:drogen},
}
\end{filecontents}

%%% tools part - tools & gimmiks %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\newcommand{\xset@killfirst}[1]{}
\long\def\@noneofthree#1#2#3{}
\long\def\@firstofthree#1#2#3{#1}
\long\def\@secondofthree#1#2#3{#2}
\long\def\@thirdofthree#1#2#3{#3}% Is defined in `latex.ltx` already as \@threeofthree
\global\let\xset@message\message% message handler

% missing biblatex macros:
% {<field>}{<csname>}
\protected\def\blx@imc@restorefieldcs#1{\csletcs{abx@field@#1}}
% {<name>}{<csname>}
\protected\def\blx@imc@restorenamecs#1{\csletcs{abx@name@#1}}
% {<list>}{<csname>}
\protected\def\blx@imc@restorelistcs#1{\csletcs{abx@list@#1}}
% update biblatex
\blx@regimcs{\restorefieldcs \restorenamecs \restorelistcs}

% missing etoolbox macros:
\def\iftoggleexists#1{\ifcsdef{etb@tgl@#1}{\@firstoftwo}{\@secondoftwo}}
%\def\removetoggle#1{\iftoggleexists{#1}{\cslet{etb@tgl@#1}{\undefined}{}}}

%%% keyval part - the options %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% register given field|names|lists denotifier in a etoolbox-list
\newcommand{\xset@savetolist}[2]{% <list> {<item>,...,<item>}
  \renewcommand{\do}[1]{\ifinlist{##1}{#1}%
    {\xset@message{^^Jxset Warning: Option value ##1 already in dataname list - skipping^^J}}%
    {\listadd{#1}{##1}}}\docsvlist{#2}}
% remove given field|names|lists denotifier from a etoolbox-list
\newcommand{\xset@removefromlist}[2]{% <list> {<item>,...,<item>}
  \renewcommand{\do}[1]{\ifinlist{##1}{#1}%
    {\listremove{#1}{##1}}%
    {\xset@message{^^Jxset Warning: Option value ##1 not found in dataname list - skipping^^J}}}\docsvlist{#2}}

% userside
\newcommand{\DeclareXsetOptions}[1]{\setkeys{xset@opt}{#1}}

% keys
\define@key{xset@opt}{ontop}{\xset@savetolist{\xset@opt@ontop}{#1}}
\define@key{xset@opt}{nottop}{\xset@removefromlist{\xset@opt@ontop}{#1}}
\define@key{xset@opt}{unhide}{\xset@savetolist{\xset@opt@unhide}{#1}}
\define@key{xset@opt}{info}{\ifstrequal{#1}{silent}{\let\xset@message\xset@killfirst\message{^^Jxset Info: Running in silent mode.^^J}}%
                           {\ifstrequal{#1}{warning}{\let\xset@message\message}%
                             {\let\xset@message\message\xset@message{^^Jxset Warning: Unknon option for info. Use 'silent' or 'warning', not '#1'.}}}}

% basic lists empty
\def\xset@opt@ontop{}
\def\xset@opt@unhide{}

% defaults
\DeclareXsetOptions{  info =  warning,
                     ontop = {author,editor,maintitle,year},
                     ontop = {yearmin,yearmax},% administraional - don't remove
                    unhide =  year }

\providetoggle{xset@patcherror}% safetybreak
\providetoggle{xset@hide@@year}% yearspan hack

%%% bibliography part - manipulating sets %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% step 1: patch the new set driver
% patch entryset
\xpatchbibdriver{set}
  {\entryset}% https://tex.stackexchange.com/a/387849/118739
  {\xsetParseSet%
     \usebibmacro{author/editor+others/translator+others}%
     \iffieldundef{maintitle}{}%
       {\setunit{\printdelim{nametitledelim}}\newblock%
        \usebibmacro{maintitle+title}}%
     \iffieldundef{yearmin}{}%
       {\newunit\newblock\ifnum\numexpr\strfield{yearmin}<\numexpr\strfield{yearmax}%
          \printfield{yearmin}--\printfield{yearmax}\global\togglefalse{xset@hide@@year}\else%
          \printfield{yearmin}\global\toggletrue{xset@hide@@year}\fi% all same year - hide
          \global\undef{\xset@savedmin@year}\global\undef{\xset@savedmax@year}% make cleanup a bit quicker...
       }%
     \iffieldundef{url}{}{\newunit\newblock\printfield{url}}%
     \par\entryset}%
  {}{\errmessage{xset error: unable to apply entryset-patch to set driver}\toggletrue{xset@patcherror}}% can not be overwritten: major error

% patch finentry
\xpatchbibdriver{set}
  {\finentry}% https://tex.stackexchange.com/a/387849/118739
  {\xset@cleanup\finentry}%
  {}{\errmessage{xset error: unable to apply finentry-patch to set driver}\toggletrue{xset@patcherror}}% can not be overwritten: major error

\newcommand{\xset@usehideflags@i}[2]{% execute toggle based data deletion
  \iftoggleexists{#1}{\iftoggle{#1}{\clearfield{#2}\clearname{#2}\clearlist{#2}}{}}{}}% brute force
\newcommand{\xset@usehideflags}{%
  \renewcommand{\do}[1]{\edef\xset@tempa{xset@hide@\strfield{entrykey}@##1}%
    \expandafter\xset@usehideflags@i\expandafter{\xset@tempa}{##1}}%
  \dolistloop{\xset@opt@ontop}%
  \xset@usehideflags@i{xset@hide@@year}{year}% don't forget the manipulated year toggle
  }

\newcommand{\xset@modifyusebibmacros}{%
\renewbibmacro*{maintitle+title}{%
  \iffieldundef{maintitle}%
    {\iffieldundef{volume}%
       {}%
       {\printfield{volume}%
        \printfield{part}%
        \setunit{\addcolon\space}}%
    }%
    {\usebibmacro{maintitle}%
     \newunit\newblock%
     \iffieldundef{volume}%
       {}%
       {\printfield{volume}%
        \printfield{part}%
        \setunit{\addcolon\space}}}%
  \usebibmacro{title}\newunit%
  \clearfield{volume}\clearfield{part}}%
}

% step 2: patch the entry drivers
\renewcommand{\do}[1]{\xpatchbibdriver{#1}%
  {\usebibmacro{begentry}}%
  {\usebibmacro{begentry}\xset@usehideflags\xset@modifyusebibmacros}%
  {}{\errmessage{xset error: unable to patch #1 driver}\toggletrue{xset@patcherror}}}%
\docsvlist{book,booklet}% <- names of subentry drivers to patch, add what is needed here

%%% xset part - collecting and manipulating data %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% <set id> <entry id> <data name>
\newcommand{\xset@checkfield}[3]{% treat special fields different
% a) year: save min and max for <min>-<max> todo: convert to biberlike notation <low year>/<high year> and drop all min/max garbage
  \ifstrequal{#3}{year}{\ifcsvoid{xset@savedmin@#3}{\savefieldcs{#3}{xset@savedmin@#3}\savefieldcs{#3}{xset@savedmax@#3}}%
    {\ifnum\numexpr\strfield{#3}>\numexpr\csuse{xset@savedmax@#3}%
       \savefieldcs{#3}{xset@savedmax@#3}\else\ifnum\numexpr\strfield{#3}<\numexpr\csuse{xset@savedmin@#3}%
       \savefieldcs{#3}{xset@savedmin@#3}\fi\fi}}{\ifcsvoid{xset@savedf@#3}{\savefieldcs{#3}{xset@savedf@#3}%
         \ifinlist{#3}{\xset@opt@unhide}{}{\global\providetoggle{xset@hide@#2@#3}\global\toggletrue{xset@hide@#2@#3}}}{%
         \ifinlist{#3}{\xset@opt@unhide}{}{\iffieldequalcs{#3}{xset@savedf@#3}{%
           \global\providetoggle{xset@hide@#2@#3}\global\toggletrue{xset@hide@#2@#3}}{% data mismatch! might be an error?
           \xset@message{^^Jxset Warning: Name mismatch in #3 (set #1 / entry #2) - check result!^^J}}}}}}

% <set id> <entry id> <data name>
\newcommand{\xset@checknames}[3]{\ifcsvoid{xset@savedn@#3}{\savenamecs{#3}{xset@savedn@#3}\csxdef{xset@savedc@#3}{\arabic{#3}}% save
% removed set id from toggle name (was: xset@hide@#1@#2@#3)
  \ifinlist{#3}{\xset@opt@unhide}{}{\global\providetoggle{xset@hide@#2@#3}\global\toggletrue{xset@hide@#2@#3}}}{%
  % remember to hide later
  \ifinlist{#3}{\xset@opt@unhide}{}{\ifnameequalcs{#3}{xset@savedn@#3}{% name already saved and equal - remember to hide later
    \global\providetoggle{xset@hide@#2@#3}\global\toggletrue{xset@hide@#2@#3}}{% data mismatch! might be an error?
    \xset@message{^^Jxset Warning: Name mismatch in #3 (set #1 / entry #2) - check result!^^J}}}}}

\newcommand{\xset@checklists}[3]{\ifcsvoid{xset@savedl@#3}{\savelistcs{#3}{xset@savedl@#3}\csxdef{xset@savedc@#3}{\arabic{#3}}}{}}

% <set id> <set children id list> <field name>
\newcommand{\xset@dataparser}[3]{\edef\xset@setid{#1}% remember set ID
  \bgroup\renewcommand{\do}[1]{%
    \ifentryinbib{##1}{\entrydata{##1}%
         {\iffieldundef{#3}{\iflistundef{#3}{\ifnameundef{#3}{}{%
            \xset@checknames{\xset@setid}{##1}{#3}}}{\xset@checklists{\xset@setid}{##1}{#3}}}{\xset@checkfield{\xset@setid}{##1}{#3}}}%
      }{\xset@message{^^Jxset Warning: Entry ##1 of set #1 not found...^^J}}}%
  \edef\xset@tempa{#2}\expandafter\docsvlist\expandafter{\xset@tempa}%
  \egroup}

\newcommand{\xsetParseSet}{%
  \renewcommand{\do}[1]{\xset@dataparser{\thefield{entrykey}}{\thefield{entryset}}{##1}%
    \ifcsvoid{xset@savedf@##1}{% now the trick: restore all collected data
    \ifcsvoid{xset@savedn@##1}{% but now we are in the set scope...
    \ifcsvoid{xset@savedl@##1}{}{\restorelistcs{##1}{xset@savedl@##1}\setcounter{##1}{\csuse{xset@savedc@##1}}}}%
                                {\restorenamecs{##1}{xset@savedn@##1}\setcounter{##1}{\csuse{xset@savedc@##1}}}}%
                                {\restorefieldcs{##1}{xset@savedf@##1}}%
    \ifcsvoid{xset@savedmin@##1}{}{\ifstrequal{##1}{year}{\restorefieldcs{##1min}{xset@savedmin@##1}% year is different
                                                          \restorefieldcs{##1max}{xset@savedmax@##1}}{}}}%
  \dolistloop{\xset@opt@ontop}}%

\newcommand{\xset@cleanup}{% cleanup after set
  \renewcommand{\do}[1]{%
    \global\csundef{xset@savedf@##1}\global\csundef{xset@savedn@##1}\global\csundef{xset@savedl@##1}}% absolut brute force - no checks
  \dolistloop{\xset@opt@ontop}%
  \global\togglefalse{xset@hide@@year}% reset special global toggles
  }
\makeatother

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% this is the user-level part %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% include bib data
\addbibresource{\jobname.bib}

% a little bit of design
\renewcommand{\entrysetpunct}{\par\nobreak}
\DeclareFieldFormat{bibentrysetcount}{\makebox[1em][l]{\smash{\mknumalph{#1})}}\ignorespaces}
\DeclareFieldFormat[set]{editortype}{\mkbibparens{#1}}

% control xset behaviour
\DeclareXsetOptions{ ontop = {url,publisher} }% tell xset what to put on top...

% create output
\begin{document}\nocite{*}
\printbibliography
\end{document}

It should be easy to implement new fields/lists with standard or special treatment. One thing I still have not managed to get solved is collecting names and the absolutely annoying additional space between the 'a)' and the 'Asterix'... :-(

But at least - maybe this is going to help someone to deal with the really basic implementation of sets in biblatex...

You could also think about collecting namefields (that one might break biblatex' sorting routines)...

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.