PageSourceSearch

https://www.marcusbannerman.co.uk/js/bib-publication-list/src/bib-publication-list.js

js marcusbannerman.co.uk collected 2026-10-07 19:07:55 UTC 20,063 bytes, 474 lines download raw bytes

1var bibtexify = (function($) {
2    // helper function to "compile" LaTeX special characters to HTML
3    var htmlify = function(str) {
4        // TODO: this is probably not a complete list..
5        str = str.replace(/\\"\{a\}/g, 'ä')
6            .replace(/\{\\aa\}/g, 'å')
7            .replace(/\\aa\{\}/g, 'å')
8            .replace(/\\"a/g, 'ä')
9            .replace(/\\"\{o\}/g, 'ö')
10            .replace(/\\'e/g, 'é')
11            .replace(/\\'\{e\}/g, 'é')
12            .replace(/\\'a/g, 'á')
13            .replace(/\\'A/g, 'Á')
14            .replace(/\\"o/g, 'ö')
15            .replace(/\\ss\{\}/g, 'ß')
16            .replace(/\{/g, '')
17            .replace(/\}/g, '')
18            .replace(/\\&/g, '&')
19            .replace(/--/g, '–')
20            .replace(/\{\\textperiodcentered\}/g, '·')
21            .replace(/\\textperiodcentered/g, '·')
22	;
23        return str;
24    };
25    var uriencode = function(str) {
26        // TODO: this is probably not a complete list..
27        str = str.replace(/\\"\{a\}/g, '%C3%A4')
28            .replace(/\{\\aa\}/g, '%C3%A5')
29            .replace(/\\aa\{\}/g, '%C3%A5')
30            .replace(/\\"a/g, '%C3%A4')
31            .replace(/\\"\{o\}/g, '%C3%B6')
32            .replace(/\\'e/g, '%C3%A9')
33            .replace(/\\'\{e\}/g, '%C3%A9')
34            .replace(/\\'a/g, '%C3%A1')
35            .replace(/\\'A/g, '%C3%81')
36            .replace(/\\"o/g, '%C3%B6')
37            .replace(/\\ss\{\}/g, '%C3%9F')
38            .replace(/\{/g, '')
39            .replace(/\}/g, '')
40            .replace(/\\&/g, '%26')
41            .replace(/--/g, '%E2%80%93');
42        return str;
43    };
44
45    // event handlers for the bibtex links
46    var EventHandlers = {
47        showbib: function showbib(event) {
48            $(this).next(".bibinfo").removeClass('hidden').addClass("open");
49            $("#shutter").show();
50            event.preventDefault();
51        },
52        hidebib: function hidebib(event) {
53            $("#shutter").hide();
54            $(".bibinfo.open").removeClass("open").addClass("hidden");
55            event.preventDefault();
56        }
57    };
58
59    var Bib2HTML = function(data, $pubTable, options) {
60        this.options = options;
61        this.$pubTable = $pubTable;
62        this.stats = { };
63        this.initialize(data);
64    };
65
66    var bibproto = Bib2HTML.prototype;
67
68    // helper functions to turn a single bibtex entry into HTML
69    bibproto.bib2html = {
70        // the main function which turns the entry into HTML
71        entry2html: function(entryData, bib) {
72            var type = entryData.entryType.toLowerCase();
73            // default to type misc if type is unknown
74            if(!(type in bib.bib2html)) {
75                type = 'misc';
76                entryData.entryType = type;
77            }
78            var itemStr = htmlify(bib.bib2html[type](entryData, bib));
79            itemStr += bib.bib2html.links(entryData, bib);
80	    if (bib.options.showbibtex)
81		itemStr += bib.bib2html.bibtex(entryData);
82            if (bib.options.tweet && entryData.url) {
83                itemStr += bib.bib2html.tweet(entryData, bib);
84            }
85            return itemStr.replace(/undefined/g, '<span class="undefined">missing<\/span>');
86        },
87        // converts the given author data into HTML
88        authors2html: function(entryData, bib) {
89	    if (!(entryData.author instanceof Array) || (entryData.author.length == 0)) {
90		if (entryData.editor)
91		    return entryData.editor;
92		else
93		    return "Missing author/editor";
94	    }
95	    
96            var authorsStr = '';
97	    if (entryData.author.length == 1)
98		authorsStr = bib.bibtex._formatAuthor(entryData.author[0]);
99	    else if (entryData.author.length == 2)
100		authorsStr = bib.bibtex._formatAuthor(entryData.author[0]) + " and " + bib.bibtex._formatAuthor(entryData.author[1]);
101	    else {
102		for (var index = 0; index < entryData.author.length - 1; index++) {
103                    if (index > 0) { authorsStr += ", "; }
104                    authorsStr += bib.bibtex._formatAuthor(entryData.author[index]);
105		}
106                authorsStr += ", and " + bib.bibtex._formatAuthor(entryData.author[entryData.author.length - 1]);
107	    }
108            return htmlify(authorsStr);
109        },
110        // converts the given author data into HTML
111        authors2shorthtml: function(entryData, bib) {
112	    if (!(entryData.author instanceof Array) || (entryData.author.length == 0)) {
113		if (entryData.editor)
114		    return entryData.editor;
115		else
116		    return "Missing author/editor";
117	    }
118            var authorsStr = '';
119	    if (entryData.author.length == 1)
120		authorsStr = entryData.author[0].last;
121	    else if (entryData.author.length == 2)
122		authorsStr = entryData.author[0].last + " and " + entryData.author[1].last;
123	    else
124		authorsStr = entryData.author[0].last + " et al.";
125            return htmlify(authorsStr);
126        },
127	shortcite: function(entryData, bib) {
128	    var year;
129	    if (entryData.year == "To Appear")
130		year = "";
131	    else
132		year = " (" + entryData.year + ")";
133	    
134	    if (entryData.author)
135		return this.authors2shorthtml(entryData, bib) + year;
136	    else
137		return entryData.title + " (" + entryData.year + ")";
138        },
139        // adds links to the PDF or url of the item
140        links: function(entryData, bib) {
141            var itemStr = '';
142	    if (entryData.url) {
143                itemStr += ' <a title="This article online" href="' + entryData.url +
144                    '"><span class="fa fa-external-link" style="color:#0000FF;"></span><\/a> ';
145            }
146	    if (entryData.doi) {
147                itemStr += ' <a href="http://dx.doi.org/'+entryData.doi+'" target="_blank"><span class="fa fa-link" style="color:#0000FF;"></span></a> ';
148            }
149	    if (entryData.file && bib.options.file_links) {
150		var files = entryData.file.split(";")
151		for (var fileidx = 0; fileidx < files.length; ++fileidx) {
152		    var data = files[fileidx].split(":");
153		    //data[0] name of file, data[1] path to file, data[2]={PDF} file type
154		    switch (data[2]) {
155		    case "PDF":
156			itemStr += '<a href="/static/literature/'+data[1]+'" target="_blank"><span class="fa fa-file-pdf-o" style="color:#e34947;"></span></a> ';
157			break;
158		    default:
159			itemStr += '<a href="/static/literature/'+data[1]+'" target="_blank"><span class="fa fa-file-o"></span></a> ';
160			break;
161		    }
162		}
163	    }
164
165            return itemStr;
166        },
167        // adds the bibtex link and the opening div with bibtex content
168        bibtex: function(entryData, bib) {
169            var itemStr = '';
170            itemStr += ' (<a title="This article as BibTeX" href="#" class="biblink">' +
171                'bib</a>)<div class="bibinfo hidden">';
172            itemStr += '<a href="#" class="bibclose" title="Close">x</a><pre>';
173            itemStr += '@' + entryData.entryType + "{" + entryData.cite + ",\n";
174            $.each(entryData, function(key, value) {
175                if (key == 'author') {
176                    itemStr += '  author = { ';
177                    for (var index = 0; index < value.length; index++) {
178                        if (index > 0) { itemStr += " and "; }
179                        itemStr += value[index].last;
180                    }
181                    itemStr += ' },\n';
182                } else if (key != 'entryType' && key != 'cite') {
183                    itemStr += '  ' + key + " = { " + value + " },\n";
184                }
185            });
186            itemStr += "}</pre></div>";
187            return itemStr;
188        },
189        // generates the twitter link for the entry
190        tweet: function(entryData, bib) {
191            // url, via, text
192            var itemStr = ' (<a title="Tweet this article" href="http://twitter.com/share?url=';
193            itemStr += entryData.url;
194            itemStr += '&via=' + bib.options.tweet;
195            itemStr += '&text=';
196            var splitName = function(wholeName) {
197		var spl = wholeName.split(' ');
198		return spl[spl.length-1];
199            };
200            var auth = entryData.author;
201            if (auth.length == 1) {
202		itemStr += uriencode(splitName(auth[0].last));
203            } else if (auth.length == 2) {
204		itemStr += uriencode(splitName(auth[0].last) + "%26" + splitName(auth[1].last));
205            } else {
206		itemStr += uriencode(splitName(auth[0].last) + " et al");
207            }
208            itemStr += ": " + encodeURIComponent('"' + entryData.title + '"');
209            itemStr += '" target="_blank">tweet</a>)';
210            return itemStr;
211        },
212        // helper functions for formatting different types of bibtex entries
213        inproceedings: function(entryData, bib) {
214            return this.authors2html(entryData, bib) + " (" + entryData.year + "). "
215		+ entryData.title + ". In <em>" + entryData.booktitle
216                + ((entryData.address)?", " + entryData.address:"") + ".<\/em>";
217        },
218        article: function(entryData, bib) {
219            return this.authors2html(entryData, bib) + ', &ldquo;' +
220                entryData.title + ',&rdquo; <em>' + entryData.journal + "</em>, "
221		+ ((entryData.volume)? "<b>
221" + entryData.volume + "</b>, " : "")
222                + ((entryData.number) ? "(" + entryData.number + "), " : "")
223		+ ((entryData.pages) ? entryData.pages + " " : "")
224		+ ((entryData.year) ? "(" + entryData.year + ") " : "")
225		+ ((entryData.address)? entryData.address + ".":"") ;
226        },
227        misc: function(entryData, bib) {
228            return this.authors2html(entryData, bib) + " (" + entryData.year + "). " +
229                entryData.title + ". " +
230                ((entryData.howpublished)?entryData.howpublished + ". ":"") +
231                ((entryData.note)?entryData.note + ".":"");
232        },
233        mastersthesis: function(entryData, bib) {
234          return this.authors2html(entryData, bib) + ", &ldquo;" 
235	  + entryData.title + ",&rdquo; " + entryData.school + " (" + entryData.year + ")" ;
236        },
237        techreport: function(entryData, bib) {
238            return this.authors2html(entryData, bib) + ", &ldquo;" +
239                entryData.title + ",&rdquo; " + entryData.institution + ", <b>" +
240                entryData.number + "</b> (" + entryData.year + ")";
241        },
242        book: function(entryData, bib) {
243            return this.authors2html(entryData, bib) + " (" + entryData.year + "). " +
244                " <em>" + entryData.title + "<\/em>, " +
245                entryData.publisher + ", " + entryData.year +
246                ((entryData.issn)?", ISBN: " + entryData.issn + ".":".");
247        },
248        inbook: function(entryData, bib) {
249            return this.authors2html(entryData, bib) + " (" + entryData.year + "). " +
250                entryData.chapter + " in <em>" + entryData.title + "<\/em>, " +
251                ((entryData.editor)?" Edited by " + entryData.editor + ", ":"") +
252                entryData.publisher + ", pp. " + entryData.pages + "" +
253                ((entryData.series)?", <em>" + entryData.series + "<\/em>":"") +
254                ((entryData.volume)?", Vol. " + entryData.volume + "":"") +
255                ((entryData.issn)?", ISBN: " + entryData.issn + "":"") +
256                ".";
257        },
258        // weights of the different types of entries; used when sorting
259        importance: {
260            'TITLE': 9999,
261            'misc': 0,
262            'manual': 10,
263            'techreport': 20,
264            'mastersthesis': 30,
265            'inproceedings': 40,
266            'incollection': 50,
267            'proceedings': 60,
268            'conference': 70,
269            'article': 80,
270            'phdthesis': 90,
271            'inbook': 100,
272            'book': 110,
273            'unpublished': 120
274        },
275        // labels used for the different types of entries
276        labels: {
277            'article': 'Journal',
278            'book': 'Book',
279            'conference': 'Conference',
280            'inbook': 'Book chapter',
281            'incollection': '',
282            'inproceedings': 'Conference',
283            'manual': 'Manual',
284            'mastersthesis': 'Thesis',
285            'misc': 'Misc',
286            'phdthesis': 'PhD Thesis',
287            'proceedings': 'Conference proceeding',
288            'techreport': 'Technical report',
289            'unpublished': 'Unpublished'}
290    };
291    // format a phd thesis similarly to masters thesis
292    bibproto.bib2html.phdthesis = bibproto.bib2html.mastersthesis;
293    // conference is the same as inproceedings
294    bibproto.bib2html.conference = bibproto.bib2html.inproceedings;
295    
296    bibproto.initialize = function initialize(data) {
297        this.bibtex = new BibTex();
298	this.bibtex.authorstring = "FIRST VON LAST JR";
299        this.bibtex.content = data;
300        this.bibtex.parse();
301	this.entries = {};
302        var bibentries = [];
303        var entryTypes = {};
304	var len = this.bibtex.data.length;
305        jQuery.extend(true, this.bib2html, this.options.bib2html);
306        for (var index = 0; index < len; index++) {
307            var item = this.bibtex.data[index];
308	    item.id = item.cite;
309	    item.title = htmlify(item.title);
310	    if (item.author)
311		item.authors = this.bib2html.authors2html(item, this);
312
313            if (!item.year) {
314		item.year = this.options.defaultYear || "To Appear";
315            }
316            var html = this.bib2html.entry2html(item, this);
317            bibentries.push([item.year, this.bib2html.labels[item.entryType], html]);
318            entryTypes[this.bib2html.labels[item.entryType]] = item.entryType;
319            this.updateStats(item);
320
321	    if (item.cite in this.entries) {
322		console.log("Repeated cite key for this bib entry.");
323		console.log(item);
324	    }
325	    this.entries[item.cite] = item;
326        }
327
328	var self = this;
329	if (this.$pubTable) {
330            jQuery.fn.dataTableExt.oSort['type-sort-asc'] = function(x, y) {
331		var item1 = self.bib2html.importance[entryTypes[x]],
332                    item2 = self.bib2html.importance[entryTypes[y]];
333		return ((item1 < item2) ? -1 : ((item1 > item2) ?  1 : 0));
334            };
335            jQuery.fn.dataTableExt.oSort['type-sort-desc'] = function(x, y) {
336		var item1 = self.bib2html.importance[entryTypes[x]],
337                    item2 = self.bib2html.importance[entryTypes[y]];
338		return ((item1 < item2) ? 1 : ((item1 > item2) ?  -1 : 0));
339            };
340            var table = this.$pubTable.dataTable({ 'aaData': bibentries,
341						   'aaSorting': this.options.sorting,
342						   'aoColumns': [ { "sTitle": "Year" },
343								  { "sTitle": "Type", "sType": "type-sort", "asSorting": [ "desc", "asc" ] },
344								  { "sTitle": "Publication", "bSortable": false }],
345						   'bPaginate': false
346						 });
347            if (this.options.visualization) {
348		this.addBarChart();
349            }
350            $("th", this.$pubTable).unbind("click").click(function(e) {
351		var $this = $(this),
352		    $thElems = $this.parent().find("th"),
353		    index = $thElems.index($this);
354		if ($this.hasClass("sorting_disabled")) { return; }
355		$this.toggleClass("sorting_asc").toggleClass("sorting_desc");
356		if (index === 0) {
357		    table.fnSort( [[0, $thElems.eq(0).hasClass("sorting_asc")?"asc":"desc"],
358				   [1, $thElems.eq(1).hasClass("sorting_asc")?"asc":"desc"]]);
359		} else {
360		    table.fnSort( [[1, $thElems.eq(1).hasClass("sorting_asc")?"asc":"desc"],
361				   [0, $thElems.eq(0).hasClass("sorting_asc")?"asc":"desc"]]);
362		}
363            });
364            // attach the event handlers to the bib items
365            $(".biblink", this.$pubTable).on('click', EventHandlers.showbib);
366            $(".bibclose", this.$pubTable).on('click', EventHandlers.hidebib);
367	}
368    };
369    // updates the stats, called whenever a new bibtex entry is parsed
370    bibproto.updateStats = function updateStats(item) {
371        if (!this.stats[item.year]) {
372            this.stats[item.year] = { 'count': 1, 'types': {} };
373            this.stats[item.year].types[item.entryType] = 1;
374        } else {
375            this.stats[item.year].count += 1;
376            if (this.stats[item.year].types[item.entryType]) {
377                this.stats[item.year].types[item.entryType] += 1;
378            } else {
379                this.stats[item.year].types[item.entryType] = 1;
380            }
381        }
382    };
383    // adds the barchart of year and publication types
384    bibproto.addBarChart = function addBarChart() {
385        var yearstats = [], max = 0;
386        $.each(this.stats, function(key, value) {
387            max = Math.max(max, value.count);
388            yearstats.push({'year': key, 'count': value.count,
389			    'item': value, 'types': value.types});
390        });
391        yearstats.sort(function(a, b) {
392            var diff = a.year - b.year;
393            if (!isNaN(diff)) {
394		return diff;
395            } else if (a.year < b.year) {
396		return -1;
397            } else if (a.year > b.year) {
398		return 1;
399            }
400            return 0;
401        });
402        var chartIdSelector = "#" + this.$pubTable[0].id + "pubchart";
403        var pubHeight = $(chartIdSelector).height()/max - 2;
404        var styleStr = chartIdSelector +" .year { width: " +
405            (100.0/yearstats.length) + "%; }" +
406            chartIdSelector + " .pub { height: " + pubHeight + "px; }";
407        var legendTypes = [];
408        var stats2html = function(item) {
409            var types = [],
410                str = '<div class="year">',
411                sum = 0;
412            $.each(item.types, function(type, value) {
413		types.push(type);
414		sum += value;
415            });
416            types.sort(function(x, y) {
417		return this.bib2html.importance[y] - this.bib2html.importance[x];
418            });
419            str += '<div class="filler" style="height:' + ((pubHeight+2)*(max-sum)) + 'px;"></div>';
420            for (var i = 0; i < types.length; i++) {
421                var type = types[i];
422                if (legendTypes.indexOf(type) === -1) {
423                    legendTypes.push(type);
424                }
425                for (var j = 0; j < item.types[type]; j++) {
426                    str += '<div class="pub ' + type + '"></div>';
427                }
428            }
429            return str + '<div class="yearlabel">' + item.year + '</div></div>';
430        };
431        var statsHtml = "<style>" + styleStr + "</style>";
432        yearstats.forEac
432h(function(item) {
433            statsHtml += stats2html(item);
434        });
435        var legendHtml = '<div class="legend">';
436        for (var i = 0, l = legendTypes.length; i < l; i++) {
437            var legend = legendTypes[i];
438            legendHtml += '<span class="pub ' + legend + '"></span>' + this.bib2html.labels[legend];
439        }
440        legendHtml += '</div>';
441        $(chartIdSelector).html(statsHtml).after(legendHtml);
442    };
443
444    // Creates a new publication list to the HTML element with ID
445    // bibElemId. The bibsrc can be
446    //   - a jQuery selector, in which case html of the element is used
447    //     as the bibtex data
448    //   - a URL, which is loaded and used as data. Note, that same-origin
449    //     policy restricts where you can load the data.
450    // Supported options: 
451    //   - visualization: A boolean to control addition of the visualization.
452    //                    Defaults to true.
453    //   - tweet: Twitter username to add Tweet links to bib items with a url field.
454    //   - sorting: Control the default sorting of the list. Defaults to [[0, "desc"], 
455    //              [1, "desc"]]. See http://datatables.net/api fnSort for details 
456    //              on formatting.
457    //   - bib2html: Can be used to override any of the functions or properties of
458    //               the bib2html object. See above, starting around line 40.
459    return function(bibsrc, opts, bibElemId) {
460        var options = $.extend({},
461			       {'visualization': false, 'sorting': [[0, "desc"], [1, "desc"]], file_links:false, showbibtex:false},
462                               opts);
463	
464	if (bibElemId) {
465	    if (options.visualization) {
466		$pubTable.before('<div id="' + bibElemId + 'pubchart" class="bibchart"></div>');
467            }
468	    
469            bibElemId = $("#" + bibElemId).addClass("bibtable");
470	}
471	
472        return new Bib2HTML(bibsrc, bibElemId, options);
473    };
474})(jQuery);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.