Biblify: Automated citations and bibliographies in webpages

or, BibTeX in the browser

J. McKenzie Alexander
jalex@lse.ac.uk

This is a demonstration of a JavaScript package providing a way to insert citations(both author-year and 'vancouver' style) and bibliographies into webpages from a BibTeX bibliography file. All of the heavy lifting is done by citation.js, which interprets the BibTeX entries and formats the bibliography. What this package does is scan the webpage text for citation keys which conform to basic natbib syntax: \cite, \citet, \citep, \nocite, \fullcite, or \citeauthor. There are limitations to how the first three are implemented, as I'm not trying to write a complete replacement for the natbib package. For complicated citations, there is an alternative format described below.

Here is a basic demonstration. The text in the left column shows the literal text which is included in the source of the webpage. The text on the right shows the way it is formatted by Biblify. What you see, here, is trivial cosmetic formatting but, behind the scenes, the package also extracts the relevant entry from a BibTeX bibliography file and gets ready to format a bibliography using citation.js. The bibliography can inserted anywhere in the webpage. In this case, there is a main bibliography at the end of the page. However, partial bibliographies contained within any HTML element can be extracted and inserted, allowing for section-by-section bibliographies to be created, as well.

Chicago APA BJPS \­cite{Alexander:2007} \cite{Alexander:2007} \cite{Alexander:2007} \cite{Alexander:2007} \­cite[pg. 120]{Morley:2016} \cite[pg. 120]{Morley:2016} \cite[pg. 120]{Morley:2016} \cite[pg. 120]{Morley:2016} \­citet{Schuppli/Fraser:2007} \citet{Schuppli/Fraser:2007} \citet{Schuppli/Fraser:2007} \citet{Schuppli/Fraser:2007} \­citet{Glinow/etal:2004} \citet{Glinow/etal:2004} \citet{Glinow/etal:2004} \citet{Glinow/etal:2004} \­citet[pg. 120]{Morley:2016} \citet[pg. 120]{Morley:2016} \citet[pg. 120]{Morley:2016} \citet[pg. 120]{Morley:2016} \­citet{Quine:1948, Quine:1960} \citet{Quine:1948, Quine:1960} \citet{Quine:1948, Quine:1960} \citet{Quine:1948, Quine:1960} \­citep[pg. 120]{Quine:1948} \citep[pg. 120]{Quine:1948} \citep[pg. 120]{Quine:1948} \citep[pg. 120]{Quine:1948} \­citep[see][]{Quine:1948} \citep[see][]{Quine:1948} \citep[see][]{Quine:1948} \citep[see][]{Quine:1948} \­citep[][pg. 120]{Quine:1948} \citep[][pg. 120]{Quine:1948} \citep[][pg. 120]{Quine:1948} \citep[][pg. 120]{Quine:1948} \­citep[see][pg. 120]{Quine:1948} \citep[see][pg. 120]{Quine:1948} \citep[see][pg. 120]{Quine:1948} \citep[see][pg. 120]{Quine:1948} \­nocite{Skyrms:1982,Skyrms:1984} \nocite{Skyrms:1982,Skyrms:1984} \nocite{Skyrms:1982,Skyrms:1984} \nocite{Skyrms:1982,Skyrms:1984}
\­fullcite{Glinow/etal:2004}
\fullcite{Glinow/etal:2004}
\­citep{Skyrms:1982, Skyrms:1984, Skyrms:1988, Quine:1948, Quine:1960, Anscombe:1958, Anscombe:1963, Anscombe:1979}
\citep{Skyrms:1982, Skyrms:1984, Skyrms:1988, Quine:1948, Quine:1960, Anscombe:1958, Anscombe:1963, Anscombe:1979}
\­citet{Skyrms:1982, Skyrms:1984, Skyrms:1988, Quine:1948, Quine:1960, Anscombe:1958, Anscombe:1963, Anscombe:1979}
\citet{Skyrms:1982, Skyrms:1984, Skyrms:1988, Quine:1948, Quine:1960, Anscombe:1958, Anscombe:1963, Anscombe:1979}
\­fullcite{Glinow/etal:2004}
\fullcite{Glinow/etal:2004}
\­citep{Skyrms:1982, Skyrms:1984, Skyrms:1988, Quine:1948, Quine:1960, Anscombe:1958, Anscombe:1963, Anscombe:1979}
\citep{Skyrms:1982, Skyrms:1984, Skyrms:1988, Quine:1948, Quine:1960, Anscombe:1958, Anscombe:1963, Anscombe:1979}
\­citet{Skyrms:1982, Skyrms:1984, Skyrms:1988, Quine:1948, Quine:1960, Anscombe:1958, Anscombe:1963, Anscombe:1979}
\citet{Skyrms:1982, Skyrms:1984, Skyrms:1988, Quine:1948, Quine:1960, Anscombe:1958, Anscombe:1963, Anscombe:1979}
\­fullcite{Glinow/etal:2004}
\fullcite{Glinow/etal:2004}
\­citep{Skyrms:1982, Skyrms:1984, Skyrms:1988, Quine:1948, Quine:1960, Anscombe:1958, Anscombe:1963, Anscombe:1979}
\citep{Skyrms:1982, Skyrms:1984, Skyrms:1988, Quine:1948, Quine:1960, Anscombe:1958, Anscombe:1963, Anscombe:1979}
\­citet{Skyrms:1982, Skyrms:1984, Skyrms:1988, Quine:1948, Quine:1960, Anscombe:1958, Anscombe:1963, Anscombe:1979}
\citet{Skyrms:1982, Skyrms:1984, Skyrms:1988, Quine:1948, Quine:1960, Anscombe:1958, Anscombe:1963, Anscombe:1979}
\­citeauthor{Skyrms:1982} \citeauthor{Skyrms:1982} \­citeauthor{Lehrer/Wagner:1981} \citeauthor{Lehrer/Wagner:1981} \­citeauthor{Laslett/etal:1972} \citeauthor{Laslett/etal:1972} \­citeauthor*{Laslett/etal:1972} \citeauthor*{Laslett/etal:1972}

How to use it

First, download Biblify. Second, either download a copy of citation.js or link to it from a server. And add jQuery. Assuming you link to citation.js from a server, put the following in your webpage:


<script src="https://cdn.jsdelivr.net/npm/citation-js@0.7.14/build/citation.min.js"></script>
<script src="https://code.jquery.com/jquery-3.7.1.min.js" 
	    integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo=" 
	    crossorigin="anonymous"></script>
<script src="./path/to/biblify.js"></script>
	

You need to load the files in that order, as citation.min.js defines some functions which are used by biblify.js during its initialisation. You also need jQuery loaded... Once you've done that, you can quickly configure Biblify by telling it where to find your BibTeX bibliography. By default, it will process all the citations in the page and append them to the end of the page in a bibliography formatted in the Chicago-style.


<script>
	Biblify.configure({
		bibfile: './my_bibliography.bib'
	});
</script>
	

Additional options are:

Option Default Description addTemplate [] This should be set to an array of dictionaries specifying the path to the CSL template file to add, the name for it, and — if needed — a function which does additional post-processing on the string generated. bibfile undefined This should be set to a string containing the relative path to the BibTeX bibliography file. The default value of undefined means an error will be thrown unless a valid path is provided. defer false A boolean value indicating whether to defer processing citations on the page. The default value of false means that processing will begin as soon as the bibliography file is loaded and parsed. A value of true means that you are going to trigger the processing of citations yourself. placement 'body' A CSS selector identifying the element to which the bibliography should be appended once all of the citations have been found. If defer is true, then this option has no effect. scan 'body' A CSS class selector indicating which element should have its text nodes scanned for citations. The default value means that the entire web page is scanned. If defer is true, then this option has no effect. template 'chicago' A string stating what bibliography format to use. The options included in Biblify are "apa" (American Psychological Association), "harvard1" (Harvard style), "vancouver" (Vancouver style), "ajp" (Australasian Journal of Philosophy), "bjps" (British Journal for the Philosophy of Science), and "chicago" (Chicago style)

Once Biblify is configured, it will load the bibliography file from the server (along with any additional templates) and start parsing the file unless the option defer is set to true. If you want to have fine-grain control over what elements are parsed and where the bibliographies go — for example, if you want to have multiple bibliographies (e.g., you have a markdown file containing comments on essays, and you want each set of comments to have its own bibliography) — you need to defer.

If your bibliography file is large, it can take some time before it's ready to use. Not a lot of time from a human perspective, but long enough that if you wrote some JavaScript and had it immediately executed you might get errors because the bibliography file hasn't finished parsing. If you haven't deferred, you don't need to worry because Biblify will hold off on searching for citations until it's good to go.

However, if you are going to manually construct bibliographies yourself, how do you know when to start? Biblify triggers a 'bibliography-ready' event once the bibliography file has been parsed and any additional templates have been loaded. You can register an event listener to run after that even has been fired.

Here's how to replicate the default behaviour of Biblify with deferring:


<script>
	Biblify.configure({
		bibfile: './my_bibliography.bib',
		defer: true
	});
	
	document.addEventListener('bibliography-ready', function() {
		Biblify.processCitations('body');
		Biblify.insertBibliography('body');
	});
</script>
	

The processCitations() function can be given either a CSS selector or a javascript object representing an element node: it will recursively search all of the text nodes in that element and format all of the citations it finds. The function insertBibliography() also accepts either a CSS selector or an element node, and it will append the bibliography to that element. That's it!

Sectional bibliographies

Suppose you want to organise your bibliography by sections or subsections. How do you do that? The way you do this is to tell Biblify to only process the citations in an element other than the main body. Once it's done that, you can then put only the references found in that element someplace. In what follows, I assume that Biblify has been configured and deferred, and so I am only showing the code which does something. Here's an example:


<div id='section1'>
	<h3>This is the first section</h3>
	<p>
		If you read only two essays by Quine, you
		should read \­cite{Quine:1948} and 
		\­cite{Quine:1951}, as they are classics.
	</p>
</div>
<strong>References.</strong>
<div id='bib1'></div>

<div id='section2'>
	<h3>This is the second section</h3>
	<p>
		If you read only two essays by Anscombe, you
		should read  \­cite{Anscombe:1958} and \­cite{Anscombe:1979}, 
		as they are classics.
	</p>
</div>
<strong>References.</strong>
<div id='bib2'></div>

<script>
	document.addEventListener('bibliography-ready', function() {
		Biblify.processCitations('#section1');
		Biblify.insertBibliography('#bib1');
		Biblify.clearCitations();
		Biblify.processCitations('#section2');
		Biblify.insertBibliography('#bib2');
	});
</script>
			
	

This is the first section

If you read only two essays by Quine, you should read \cite{Quine:1948} and \cite{Quine:1951}, as they are classics.

References.

This is the second section

If you read only two essays by Anscombe, you should read \cite{Anscombe:1958} and \cite{Anscombe:1979}, as they are classics.

References.

In order to clear the citations before processing the second section, it is necessary to call Biblify.clearCitations() in order to empty the list of citations.

There's a more efficient way to create sectional bibliographies, taking into account the two necessary functions accept nodes as arguments.


	<section>
		<h3>This is the first section</h3>
		<p>
			If you read only two essays by Quine, you
			should read \­cite{Quine:1948} and 
			\­cite{Quine:1951}, as they are classics.
		</p>
	</section>
	
	<section>
		<h3>This is the second section</h3>
		<p>
			If you read only two essays by Anscombe, you
			should read  \­cite{Anscombe:1958} and \­cite{Anscombe:1979}, 
			as they are classics.
		</p>
	</section>
	
	<script>
		document.addEventListener('bibliography-ready', function() {
			$("section").each(function() {
				Biblify.clearCitations();
				Biblify.processCitations(this);
				$(this).append("<strong>References</strong>");
				Biblify.insertBibliography(this);
			});
		});
	</script>
				
		

This is the first section

If you read only two books by Quine, you should read \cite{Quine:1960} and \cite{Quine:1961}, as they are classics.

This is the second section

If you read only two books by Anscombe, you should read \cite{Anscombe:1963} and \cite{Anscombe:1981}, as they are classics.

Even if you throw away the citations, Biblify remembers all the citations that it has found so far. If you want to insert all of the citations it has found in a master bibliography at the end of the page, simply call Biblify.insertBibliography(selector, { all: true }).

Changing the citation style

You can change the citation and bibliography style by calling Biblify.useTemplate() between passes and specifying the name of a different template. The next two sections demonstrate that.

APA format

The quick brown fox jumped over the lazy dogs. The quick brown fox \citep{Allais:1953a}, according to \citet[pg. 120]{Baratella:2023}, did in fact \citep[see][]{Carnap/etal:1929} jump over the lazy dogs \citep{Bergstrom/Bergstrom:1999}. The quick brown fox, according to \cite{Bohman:1991} and \citet{Frank:1995b}, did in fact jump \citep{Dagg:2004} over the lazy dog \citep{Ehrlich:2012}. The quick, argued \cite{Glinow/etal:2004}, and brown fox \citep{Loy/etal:2020} did supposed jump, said \citet{Schuppli/Fraser:2007} over the lazy dog \citep{MaynardSmith:1968}.

References

Harvard format

The quick brown fox jumped over the lazy dogs. The quick brown fox \citep{Allais:1953a}, according to \citet[pg. 120]{Baratella:2023}, did in fact \citep[see][]{Carnap/etal:1929} jump over the lazy dogs \citep{Bergstrom/Bergstrom:1999}. The quick brown fox, according to \cite{Bohman:1991} and \citet{Frank:1995b}, did in fact jump \citep{Dagg:2004} over the lazy dog \citep{Ehrlich:2012}. The quick, argued \cite{Glinow/etal:2004}, and brown fox \citep{Loy/etal:2020} did supposed jump, said \citet{Schuppli/Fraser:2007} over the lazy dog \citep{MaynardSmith:1968}.

References

Vancouver-style citations

So-called Vancouver style citations are very different from author-year. Here, the bibliography items are listed in the order in which they are cited in the text, and inline citations are by their numerical index, appearing between parentheses or brackets, as a superscript or as normal text. Multiple references are sorted according to their order with ranges collapsed whenever possible. Here is an example:

Lorem ipsum dolor, sit amet consectetur adipisicing elit.\cite{Pfeffer:2015} Quis at sed, perferendis sequi accusantium est velit,\cite{Allais:1953a, Baratella:2023, Carnap/etal:1929} eius saepe neque ullam dolorum,\cite{Bergstrom/Bergstrom:1999} molestias, odio praesentium non a dolorem aut necessitatibus consectetur? Voluptatibus ratione unde blanditiis fugit distinctio hic fugiat, quibusdam velit pariatur, qui illo, quis earum suscipit\cite{Bohman:1991, Frank:1995b, Dagg:2004, Ehrlich:2012}. Necessitatibus, quas sint,\cite{Hains:2016, Kruse:2013} sit ea aspernatur fugit perspiciatis nihil enim suscipit accusamus provident nobis.\cite{Glinow/etal:2004, Loy/etal:2020, Schuppli/Fraser:2007, MaynardSmith:1968} Laudantium in culpa, nisi error illum facilis voluptatum voluptatibus voluptate aliquam iste inventore libero?\cite{Moore/Fresco:2012, Hofbauer/Sigmund:2002} Tenetur provident labore aliquid,\cite{Ohtsuki:2007p1203} voluptatibus vitae hic dolore,\cite{Kaneko/Suzuki:1994} quo vero tempora alias quos, repudiandae unde beatae\cite{Rodriguez:2021}.

And now, something different\cite{Carnap/etal:1929,Schuppli/Fraser:2007,Ehrlich:2012}. And see also \cite{Bohman:1991,Frank:1995b,Bohman:1991,Frank:1995b,Frank:1995b} and \cite{MaynardSmith:1968, Moore/Fresco:2012, Hofbauer/Sigmund:2002, Ohtsuki:2007p1203}. And this \cite{Bohman:1991, Frank:1995b, Dagg:2004, Glinow/etal:2004, Hofbauer/Sigmund:2002, Ohtsuki:2007p1203, Kaneko/Suzuki:1994}.

The default formatting of the Vancouver-style citations isn't very pretty, so there are some CSS selectors you can target. Each of the inline citations is positioned in a span.biblify-vancouver. The bibliography has the following structure:


<div class='csl-bib-body'>
	<div class='csl-entry'>
		<div class='csl-left-margin'>
			This is where the numerical index lives.
		</div>
		<div class='csl-right-inline'>
			This is where the full bibliography entry lives.
		</div>
	</div>
</div>
		

In order to help prettify the appearance of Vancouver-style citations, Biblify injects the following CSS at the very beginning of the web page. If you add your own CSS later, you can override it.


span.biblify-vancouver {
	font-weight: bold;
}

div.biblify-vancouver-template div.csl-entry {
	display: grid;
	grid-template-columns: 18pt 1fr;
	column-gap: 8pt;
}

div.biblify-vancouver-template div.csl-entry div.csl-right-inline {
	width: auto;
}		
		

References

In general, the bibliography generated has a class of the form 'biblify-NAME-template' added to it, where the NAME refers to the name of the template. This allows you to target different styles of bibliography with CSS, if you want.

Adding new templates

Biblify comes equipped with a few of the more standard citation styles, by default, but it is possible to install more citation styles. This is done by passing appropriate options to the configure function. Where can you find additional citation styles? Zotero has a great page with many styles available. You can download these styles and put them into the same directory as the .html file on your server.

If you want to install new styles, the way to do this is demonstrated below. You pass an additional option to configure, addTemplates, which takes as its value an array of dictionaries containing information about the new templates to install. These dictionaries have two options which must be specified: name, path, and one optional one, postprocessor.


function do_something(str) {
	// This is a silly example but it shows the idea.
	return str.toUpperCase();
}

Biblify.configure({
	bibfile: './bibliography.bib',
	template: 'chicago',
	defer: true,
	addTemplates: [
		{
			name: 'ergo',
			path: './src/ergo.csl'
		},
		{
			name: 'econometrica',
			path: './src/econometrica.csl',
			postprocessor: do_something,
		}
	]
});
	

The option name is the internal name used to refer to the template. It is the option you will pass to Biblify.useTemplate(name).

The option path is the path to the csl file stating how the bibliography should be formatted.

The reason there is an option called postprocessor is because of how Biblify works in the background. It creates the appropriate text to insert for the \­citet and \­cite values by asking citation.js to generate an inline citation for the items in the specified key — which might include several items. Consider a citation with several people, something like the following

\­citep{Skyrms:1982, Skyrms:1984, Skyrms:1988, Bergstrom/Bergstrom:1999, Anscombe:1958, Anscombe:1963, Anscombe:1979, Glinow/etal:2004}

This looks like the following, in different styles:

\citep{Skyrms:1982, Skyrms:1984, Skyrms:1988, Bergstrom/Bergstrom:1999, Anscombe:1958, Anscombe:1963, Anscombe:1979, Glinow/etal:2004}
\citep{Skyrms:1982, Skyrms:1984, Skyrms:1988, Bergstrom/Bergstrom:1999, Anscombe:1958, Anscombe:1963, Anscombe:1979, Glinow/etal:2004}
\citep{Skyrms:1982, Skyrms:1984, Skyrms:1988, Bergstrom/Bergstrom:1999, Anscombe:1958, Anscombe:1963, Anscombe:1979, Glinow/etal:2004}

Notice that there are a number of differences between these entries. One is the sort order. Another is whether there is a ',' before the first occurrence of the year, as in the APA format. This something that Biblify has to consider when it goes about constructing the appropriate content for \­citet from the citation. Biblify, internally, uses regular expressions to handle this in easy cases like APA and Chicago, but the lovingly weird BJPS format requires a little more work. Other formats might need further adjustment in ways that I cannot anticipate. The post-processor should be a function which takes a string and which returns a string.

Here are examples of Ergo and Econometrica. Recall that, for Econometrica, the \­citet output is run through the post-processor and so is now uppercase.

Here are a number of references: \cite{Brandenberger/Dekel:1987b,Kahneman/Tversky:1979,Rubinstein:1982,Sen:1977b}.

Here are a number of references: \citep{Brandenberger/Dekel:1987b,Kahneman/Tversky:1979,Rubinstein:1982,Sen:1977b}.

References

Here are a number of references: \cite{Brandenberger/Dekel:1987b,Kahneman/Tversky:1979,Rubinstein:1982,Sen:1977b}.

Here are a number of references: \citep{Brandenberger/Dekel:1987b,Kahneman/Tversky:1979,Rubinstein:1982,Sen:1977b}.

References

How it works

Biblify loads the specified BibTeX bibliography file hosted on the server (or on your local computer) and then splits it into a big array containing individual entries. It does this by breaking at empty lines, assuming that a bibliography entry is formatted something like this:


@article{	Quine:1948,
	author = {W. V. O. Quine},
	journal = {The Review of Metaphysics},
	month = {September},
	number = {5},
	pages = {21--38},
	title = {On What There Is},
	volume = {2},
	year = {1948}
}		
	

Each individual entry is stored in a string, and the key is extracted using a regular expression. A dictionary is created mapping keys to BibTeX entries, so that once the BibTeX file is parsed, looking up citations will be fast. N.B. If you use cross-references in your bibliography file, Biblify will not work. You will need to use another program like bibtool to remove the cross-references.

The code then walks through all of the individual text nodes contained within the HTML element specified, and checks to see if the text matches against a complex regular expression representing all of the elements shown above. (Note that this means it will also match meaningless expressions like \­fullnociteauthort*{somekey:1999}. If that happens, it will do something but probably not what you want. So only include the meaningful expressions.) If it finds one, it then does the following:

  1. Identifies the type of citation it is (cite, citet, citep, citeauthor, fullcite, or nocite).
  2. Extracts the key.
  3. Gets the corresponding entry from the bibliography file.
  4. Creates a Cite object using citation.js.
  5. Uses the author/editor and year information from that object to format the inline text accordingly. This actually involves injecting some HTML code into the DOM, replacing the text node with a more complicated structure. If you inspect the elements on this page, you will see that the formatted inline citation is wrapped with a <span> element with the custom attribute data-bibtex set to the particular key. This is useful because — as we will see in a moment — you can use that attribute-key pair to perform some neat manipulations.
  6. Adds that particular Cite object to a list for later insertion into the bibliography,
  7. Once all the text nodes have been processed, the bibliography is then added to the appropriate place.

Notes

Multiple keys are supported for \­cite, \­citet, \­citep, \­nocite and \­fullcite. The order of the entries is determined by the citation.js engine. Notice that if the author's name is not exactly the same, then weird things will happen: e.g., \citep{Quine:1948, Quine:1951, Quine:1961}.

The \­citeauthor interpreter constructs the authors/editors names manually, so the formatting might be a little different than what the style template would suggest.

Neat tricks

The inline citations are <span> elements with a data attribute holding the BibTeX key for the citation. It turns out that the bibliography automatically generated by citation.js also contains a reference for the BibTeX key in an attribute. This makes it possible to use a little bit of jQuery to create tooltips for each inline citation! The current page uses Shoelace for the tooltips in the paragraphs below. Move the mouse over the two references below (with the dashed line beneath) and the reference will appear in a tooltip!

This is some text which contains a citation to \citet{Layard:2005} and an oblique reference to another paper \citep[see][pp. 28-50]{Morley:2016}. Here is are two parenthetic reference: \citep{Brown/vonNeumann:1950} and \citep{vonNeumann:1928}.

In addition, the Biblify.insertBibliography() function accept an optional second argument, which is a dictionary of options affecting its behaviour. At the moment, there are two recognised keys: 'all', which says whether it should insert all of the references it has encountered on the page (true or false), and 'postprocessor', which should be a function that accepts a string as an argument and returns a string. This function is called with a string containing the html that will be inserted. You can do post-processing on this if you want to do additional work. For example, the main bibliography at the end of this page is post-processed using Autolinker.js to convert any URLs contained in the bibliography into working links.

Minimising the load time

If your main BibTeX bibliography contains a lot of files, most of them won't be used. Since Biblify loads the bibliography file in its entirety, this can generate unnecessary network traffic. If you want to extract just the bibliography entries which are used in a web page, Biblify provides a helper function which lets you download a bibliography file containing just the citations it has encountered. If you call this function after processing all of the references in a web page, it will contain all of the needed entries. Simply call Biblify.downloadCitations(filename) and it will download a minimal bibliography file.

Bibliography for the page