The main problem with Twitter is that everything you post there is publicly visible. Oh, wait—are you saying that’s intentional? Well, then. Anne Baillot recently mentioned on Twitter she’s numbering her publication list by hand. With Word.
Je numérote mes exposés – comme si ca allait impressionner qui que ce soit #candidature #sitaspasunehabilitation a38anstasratétavie
— Anne Baillot (@AnneBaillot) January 30, 2015
This is, of course, wrong, and at some point I may actually blog about better ways. However, her post also reminded me that I’m not quite perfect myself, as don’t have a list of publications on my Web site—only a link to my list of publications on CiteULike. In principle, that would do the job, but it’s not quite optimal, because it requires readers to follow a link to get at the information. It would be much nicer to have the list of publications right on my own Web site—but of course without requiring any additional work to keep it current.
As a discussion on Twitter showed, my motivation for doing what I describe below is not necessarily obvious, so I’ll try to be more explicit:
- I need an up-to-date list of publications on my Web site, because it is an important aspect of a researcher’s public identity.
- I also need up-to-date lists of publications for various other purposes, such as grant proposals, project reports, or job applications. These lists can be subsets of the full list, containing, for example, only publications on a specific project or topic, or from a particular period.
- In order to avoid errors, inconsistencies, and unnecessary work, all information should come from a single, authoritative database.
- Online lists should automatically retrieve the latest information from the database; again, this is to avoid errors, inconsistencies, and unnecessary work.
One particularly interesting aspect is point 2 (thanks to Cerstin Mahlow and Max Kemman for pointing this out). It’s tedious and error-prone to manually select all publications relevant for a particular purpose, so you’d rather assign tags your publications and then have the lists generated automatically on the basis of the tags. For example, the screenshot in figure 1 below shows some of the tags I’ve assigned to publications on CiteULike (not all of them my own, of course). It would be handy if I could embed a list based on a tag on a Web page, and it would always be current.
Figure 1: Some of my CiteULike tags
It turns out that this actually quite simple.1 CiteULike offers us an API that gives you one or more bibliography items in JSON format. So you just need a little JavaScript to retrieve the data and format it for display. Some reference managers, e.g., Zotero, may offer ready-made code or plug-ins for this purpose, and that’s fine if it does what you need. However, I don’t use Zotero, and I also prefer to have a lightweight and (at least for me) transparent solution for my (pretty simple) use case. Back in the last century, I wrote my own SGML-based BibTeX replacement for my master’s thesis, so I know that writing formatters for bibliography entries is not black magic. Also, the goal here is only to format the publication types occurring in my list of publications.
Here’s what I did. The basic idea is, of course, to fetch the data from CiteULike, iterate over the bibliography records, and format each record according to its publication type. Regardless of the publication type, all bibliography entries contain more or less the same data (author, title, date, etc.), so you’ll typically use a template-driven approach. In other words: you have a template for each publication type, which looks something like
%author%: %title%. %address%: %publisher%, %date%.
and then you replace the placeholders—=%author%=, %title%, etc.—with the actual data from bibliography record. Of course you also have to handle bold and italics, missing items, and other little things, but that’s basically it.
Here’s the code that implements this. queryUrl
is something like http://www.citeulike.org/json/user/mxp/publications. For convenience I use jQuery to retrieve the data.
$.ajax({ dataType: "jsonp", url: queryUrl, success: function(data) { var target = $("#biblist"); var template = ""; for (var i in data) { var type = data[i]['type']; template = templates[type]; var replace = template.match(/%.+?%/g); if (! replace) { continue; } for (var r of replace) { var key = r.match(/%(.+?)%/)[1]; template = template.replace(new RegExp(r, "g"), replacements[key](data[i])); } target.append('<li class="bibitem ' + type + '">' + template + '</li>'); } } });
We look at the type of publication, get the corresponding template from templates
, then iterate over the placeholders in the template, and replace them with whatever the corresponding function in replacements
returns.
The templates look like this:
var templates = { "BOOK": '%author_or_editor%' + '%title%' + '%publisher%' + '%address%' + '%year%' + '%doi%'+ '%href%', "INCONF": '%author%' + '%title%' + '%editor%' + '%booktitle%' + '%pages%' + '%publisher%' + '%address%' + '%year%' + '%doi%' + '%href%', "JOUR": '%author%' + '%title%' + '%journal%' + '%volume%' + '%issue%' + '%pages%' + '%year%' + '%href%', "INCOL": '%author%' + '%title%' + '%editor%' + '%booktitle%' + '%pages%' + '%publisher%' + '%address%' + '%year%' + '%doi%' + '%href%', "CHAP": '%author%' + '%title%' + '%editor%' + '%booktitle%' + '%pages%' + '%publisher%' + '%address%' + '%year%' + '%doi%' + '%href%', "THES": '%author%' + '%title%' + span('bibthesistype phd', "") + '%school%' + '%address%' + '%year%' + '%doi%'+ '%href%', "MTHES": '%author%' + '%title%' + span('bibthesistype ma', "") + '%school%' + '%address%' + '%year%' + '%doi%'+ '%href%', };
And replacements
contains the functions that get a particular datum given a bibliographic record:
var replacements = { "type": function (record) { return record['type']; }, "author": function (record) { if (record['authors']) { var plural = false; if (record['authors'].length > 1) { plural = true; } return span('bibauthor' + (plural ? ' plural' : ""), record['authors'].join(", ")); } return ""; }, "editor": function (record) { if (record['editors']) { var plural = false; if (record['editors'].length > 1) { plural = true; } return span('bibeditor' + (plural ? ' plural' : ""), record['editors'].join(", ")); } return ""; }, "author_or_editor": function (record) { return replacements['author'](record) || replacements['editor'](record); }, "doi": function (record) { if (record['doi']) { return '<a class="bibdoi" href="http://dx.doi.org/' + record['doi'] + '">' + record['doi'] + '</a>'; } else { return ""; } }, "href": function (record) { if (record['href']) { return '<a class="bibhref" href="' + record['href'] + '">CiteULike</a>'; } else { return ""; } }, "booktitle": function (record) { return span('bibbooktitle', record['booktitle'].replace(/[{}]/g, "")); }, "title": function (record) { return span('bibtitle', record['title'].replace(/[{}]/g, "")); }, "journal": function (record) { return span('bibjournal', record['journal']); }, "volume": function (record) { if (record['volume']) { return span('bibvolume', record['volume']); } return ""; }, "issue": function (record) { if (record['issue']) { return span('bibissue', record['issue']); } return ""; }, "pages": function (record) { var str = ""; var range = false; if (! record['start_page']) { return ""; } if (record['start_page']) { str = record['start_page']; } if (record['end_page']) { range = true; str += "\u2013" + record['end_page']; } return span('bibpage' + (range ? ' plural' : ""), str); }, "publisher": function (record) { if (record['publisher']) { return span('bibpublisher', record['publisher']); } return ""; }, "school": function (record) { if (record['school']) { return span('bibschool', record['school']); } return ""; }, "address": function (record) { if (record['address']) { return span('bibaddress', record['address']); } return ""; }, "year": function (record) { if (record['published']) { return span('bibyear', record['published'][0]); } return span('bibyear', "s.d."); } };
And that’s basically it; the definition of span()
is trivial:
function span (css_class, content) { return '<span class="' + css_class + '">' + content + '</span>'; }
It’s quick and dirty (I’m not a JavaScript hacker, and I’m making some assumptions about the data), but it does indeed do the job. What may be interesting, though, is the fact that the templates actually contain no formatting or punctuation. When we assign useful CSS classes to the HTML we produce, all this can be handled quite elegantly by a CSS stylesheet instead.
The JavaScript code outputs something like this:
<li class="bibitem INCONF"> <span class="bibauthor">Michael Piotrowski</span> <span class="bibtitle">From Law Sources to Language Resources</span> <span class="bibeditor plural">Caroline Sporleder, Kalliopi Zervanou</span> <span class="bibbooktitle">Proceedings of the ECAI 2010 workshop on Language Technology for Cultural Heritage, Social Sciences, and Humanities (LaTeCH 2010)</span> <span class="bibpage plural">67–71</span> <span class="bibyear">2010</span> <a class="bibhref" href="http://www.citeulike.org/user/mxp/article/7531181">CiteULike</a> </li>
So, it gives us the elements in the right order, and all the styling and punctuation is then added by a CSS stylesheet. The display then looks approximately like this (I haven’t reproduced the CiteULike link here):
Caroline Sporleder, Kalliopi Zervanou, editors. Proceedings of the ECAI 2010 workshop on Language Technology for Cultural Heritage, Social Sciences, and Humanities (LaTeCH 2010). pp. 67–71. 2010.
Here’s the content of the stylesheet:
.bibauthor::after { content: ". " } .INCONF .bibeditor::before, .INCOL .bibeditor::before, .CHAP .bibeditor::before { content: "In "; } .bibeditor::after { content: ", editor. "; } .bibeditor.plural::after { content: ", editors. "; } .bibyear::after { content: ". " } .BOOK .bibtitle { font-style: italic; } .bibtitle::after { content: ". " } .bibtitle { font-style: normal; } .bibtitle::after { content: ". " } .bibbooktitle { font-style: italic; } :not(.bibeditor) + .bibbooktitle::before { content: "In "; font-style: normal; } .bibbooktitle::after { content: ", " } .bibdoi:not(:empty)::before { content: "doi:"; } #biblist .bibitem a.bibhref { font-family: "Helvetica"; font-weight: "bold"; font-size: 75%; text-decoration: none; background-color: red; color: white; padding: 2px; border-radius: 3px; margin-left: 1em; } a.bibhref:hover { border: 1px solid black; } .bibpage::before { content: "p. "; } .bibpage.plural::before { content: "pp. "; } .bibpage::after { content: ". "; } .JOUR .bibpage::before, .JOUR .bibpages::before { content: ":"; } .bibpublisher::after, .bibschool::after { content: ", "; } .bibaddress::after { content: ", "; } .bibthesistype.phd::before { content: "PhD Thesis"; } .bibthesistype.ma::before { content: "Master’s Thesis"; } .bibthesistype::after { content: ", "; } .bibjournal { font-style: italic; } .bibjournal::after { content: ", "; } .bibissue::before { content: "("; } .bibissue::after { content: ")"; }
So, most of the formatting can be done in a declarative way (which is always good), and it also means that you can also easily localize it, by adding rules like (check out Styling using language attributes if you’re not familiar with the topic):
:lang(de) .bibeditor::after { content: ", Hrsg. "; } :lang(de) .bibeditor.plural::after { content: ", Hrsg. "; }
If you’re interested in using it, I intend to package the stuff in some way, until then you can see it in action on my publications page.
Footnotes:
For the sake of simplicity, I’ll only discuss the use case of a list of my own publications. Handling arbitrary lists is not much harder, but you have to account for multiple pages of results, because the CiteULike API gives you at most 250 items on a single page.