// Issues: // no comment handling within strings // no string concatenation // no variable values yet // Grammar implemented here: // bibtex -> (string | preamble | comment | entry)*; // string -> '@STRING' '{' key_equals_value '}'; // preamble -> '@PREAMBLE' '{' value '}'; // comment -> '@COMMENT' '{' value '}'; // entry -> '@' key '{' key ',' key_value_list '}'; // key_value_list -> key_equals_value (',' key_equals_value)*; // key_equals_value -> key '=' value; // value -> value_quotes | value_braces | key; // value_quotes -> '"' .*? '"'; // not quite // value_braces -> '{' .*? '"'; // not quite function BibtexParser() { this.pos = 0; this.input = ""; this.entries = {}; this.strings = { JAN: "January", FEB: "February", MAR: "March", APR: "April", MAY: "May", JUN: "June", JUL: "July", AUG: "August", SEP: "September", OCT: "October", NOV: "November", DEC: "December" }; this.currentKey = ""; this.currentEntry = ""; this.setInput = function (t) { this.input = t; } this.getEntries = function () { return this.entries; } this.isWhitespace = function (s) { return (s == ' ' || s == '\r' || s == '\t' || s == '\n'); } this.match = function (s) { this.skipWhitespace(); if (this.input.substring(this.pos, this.pos + s.length) == s) { this.pos += s.length; } else { throw "Token mismatch, expected " + s + ", found " + this.input.substring(this.pos); } this.skipWhitespace(); } this.tryMatch = function (s) { this.skipWhitespace(); if (this.input.substring(this.pos, this.pos + s.length) == s) { return true; } else { return false; } this.skipWhitespace(); } this.skipWhitespace = function () { while (this.isWhitespace(this.input[this.pos])) { this.pos++; } if (this.input[this.pos] == "%") { while (this.input[this.pos] != "\n") { this.pos++; } this.skipWhitespace(); } } this.value_braces = function () { var bracecount = 0; this.match("{"); var start = this.pos; while (true) { if (this.input[this.pos] == '}' && this.input[this.pos - 1] != '\\') { if (bracecount > 0) { bracecount--; } else { var end = this.pos; this.match("}"); return this.input.substring(start, end); } } else if (this.input[this.pos] == '{') { bracecount++; } else if (this.pos == this.input.length - 1) { throw "Unterminated value"; } this.pos++; } } this.value_quotes = function () { this.match('"'); var start = this.pos; while (true) { if (this.input[this.pos] == '"' && this.input[this.pos - 1] != '\\') { var end = this.pos; this.match('"'); return this.input.substring(start, end); } else if (this.pos == this.input.length - 1) { throw "Unterminated value:" + this.input.substring(start); } this.pos++; } } this.single_value = function () { var start = this.pos; if (this.tryMatch("{")) { return this.value_braces(); } else if (this.tryMatch('"')) { return this.value_quotes(); } else { var k = this.key(); //if (this.strings[k.toUpperCase()]) { if (this.strings[k]) { return this.strings[k]; } else if (k.match("^[0-9]+$")) { return k; } else { throw "Value expected:" + this.input.substring(start); } } } this.value = function () { var values = []; values.push(this.single_value()); while (this.tryMatch("#")) { this.match("#"); values.push(this.single_value()); } return values.join(""); } this.key = function () { var start = this.pos; while (true) { if (this.pos == this.input.length) { throw "Runaway key"; } if (this.input[this.pos].match("[a-zA-Z0-9_:\\./-]")) { this.pos++ } else { //return this.input.substring(start, this.pos).toUpperCase(); return this.input.substring(start, this.pos); } } } this.key_equals_value = function () { var key = this.key(); if (this.tryMatch("=")) { this.match("="); var val = this.value(); return [key, val]; } else { throw "... = value expected, equals sign missing:" + this.input.substring(this.pos); } } this.key_value_list = function () { var kv = this.key_equals_value(); this.entries[this.currentEntry][kv[0]] = kv[1]; while (this.tryMatch(",")) { this.match(","); // fixes problems with commas at the end of a list if (this.tryMatch("}")) { break; } kv = this.key_equals_value(); this.entries[this.currentEntry][kv[0]] = kv[1]; } } this.entry_body = function (d) { this.currentEntry = this.key(); this.entries[this.currentEntry] = new Object(); this.entries[this.currentEntry]["type"] = d; this.match(","); this.key_value_list(); } this.directive = function () { this.match("@"); return "@" + this.key(); } this.string = function () { var kv = this.key_equals_value(); //this.strings[kv[0].toUpperCase()] = kv[1]; this.strings[kv[0]] = kv[1]; } this.preamble = function () { this.value(); } this.comment = function () { this.value(); // this is wrong } this.entry = function (d) { this.entry_body(d); } this.bibtex = function () { while (this.tryMatch("@")) { //var d = this.directive().toUpperCase(); var d = this.directive(); this.match("{"); if (d == "@STRING") { this.string(); } else if (d == "@PREAMBLE") { this.preamble(); } else if (d == "@COMMENT") { this.comment(); } else { this.entry(d.substr(1).toLowerCase()); } this.match("}"); } } } function BibtexDisplay() { this.fixValue = function (value) { value = value.replace(/\\glqq\s?/g, "„"); value = value.replace(/\\grqq\s?/g, '”'); value = value.replace(/\\ /g, ' '); value = value.replace(/\\url/g, ''); value = value.replace(/---/g, '—'); value = value.replace(/{\\"a}/g, 'ä'); value = value.replace(/\{\\"o\}/g, 'ö'); value = value.replace(/{\\"u}/g, 'ü'); value = value.replace(/{\\"A}/g, 'Ä'); value = value.replace(/{\\"O}/g, 'Ö'); value = value.replace(/{\\"U}/g, 'Ü'); value = value.replace(/\\ss/g, 'ß'); value = value.replace(/\{(.*?)\}/g, '$1'); return value; } this.displayBibtex2 = function (i, o) { var b = new BibtexParser(); b.setInput(i); b.bibtex(); var e = b.getEntries(); var old = o.find("*"); for (var item in e) { var tpl = $(".bibtex_template").clone().removeClass('bibtex_template'); tpl.addClass("unused"); for (var key in e[item]) { var fields = tpl.find("." + key.toLowerCase()); for (var i = 0; i < fields.size(); i++) { var f = $(fields[i]); f.removeClass("unused"); var value = this.fixValue(e[item][key]); if (f.is("a")) { f.attr("href", value); } else { var currentHTML = f.html() || ""; if (currentHTML.match("%")) { // "complex" template field f.html(currentHTML.replace("%", value)); } else { // simple field f.html(value); } } } } var emptyFields = tpl.find("span .unused"); emptyFields.each(function (key, f) { if (f.innerHTML.match("%")) { f.innerHTML = ""; } }); o.append(tpl); tpl.show(); } old.remove(); } this.displayBibtex = function (input, output) { // parse bibtex input var b = new BibtexParser(); b.setInput(input); b.bibtex(); // save old entries to remove them later var old = output.find("*"); // iterate over bibTeX entries var entries = b.getEntries(); for (var entryKey in entries) { var entry = entries[entryKey]; // find template var tpl = $(".bibtex_template").clone().removeClass('bibtex_template'); // find all keys in the entry var keys = []; for (var key in entry) { //keys.push(key.toUpperCase()); keys.push(key); } // find all ifs and check them var removed = false; do { // find next if var conds = tpl.find(".if"); if (conds.size() == 0) { break; } // check if var cond = conds.first(); cond.removeClass("if"); var ifTrue = true; var classList = cond.attr('class').split(' '); $.each(classList, function (index, cls) { //if (keys.indexOf(cls.toUpperCase()) < 0) { if (keys.indexOf(cls) < 0) { ifTrue = false; } cond.removeClass(cls); }); // remove false ifs if (!ifTrue) { cond.remove(); } } while (true); // fill in remaining fields for (var index in keys) { var key = keys[index]; var value = entry[key] || ""; tpl.find("span:not(a)." + key.toLowerCase()).html(this.fixValue(value)); tpl.find("a." + key.toLowerCase()).attr('href', this.fixValue(value)); } output.append(tpl); tpl.show(); } // remove old entries old.remove(); } } function bibtex_js_draw() { $(".bibtex_template").hide(); // (new BibtexDisplay()).displayBibtex($("#bibtex_input").val(), $("#bibtex_display")); } // check whether or not jquery is present //if (typeof jQuery == 'undefined') { // // an interesting idea is loading jquery here. this might be added // // in the future. // console.error("Please include jquery in all pages using bibtex_js!"); //} else { // // draw bibtex when loaded // $(document).ready(function () { // // check for template, add default // if ($(".bibtex_template").size() == 0) { // $("body").append("
\n \n , \n \n \n \n (view online)\n \n
\n
\n \n
"); // } // // bibtex_js_draw(); // }); //}