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:
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
compared to biblatex' default output which is
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)...
@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 theknuth:ct
works inbiblatex-examples.bib
...sorttitle
, just writesorttitle = {Asterix und Obelix 01},
,sorttitle = {Asterix und Obelix 02},
etc. Alternatively if you want you can also addmaintitle
andvolume
to a custom sorting template.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 replaceA1
with something like\citekey{me}1
) somehow?\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...)@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 ofentryset
and that needs special handling.