/* * Scholar.Cite: a class for creating bibliographies from within Scholar * this class handles pulling the CSL file and item data out of the database, * while Scholar.CSL, below, handles the actual generation of the bibliography */ Scholar.Cite = new function() { this.getBibliography = getBibliography; this.getStyles = getStyles; function getStyles() { // get styles var sql = "SELECT cslID, title FROM csl ORDER BY title"; var styles = Scholar.DB.query(sql); // convert to associative array var stylesObject = new Object(); for each(var style in styles) { stylesObject[style.cslID] = style.title; } return stylesObject; } function getBibliography(cslID, items) { // get style var sql = "SELECT csl FROM csl WHERE cslID = ?"; var style = Scholar.DB.valueQuery(sql, [cslID]); // get item arrays var itemArrays = new Array(); for(var i in items) { itemArrays.push(items[i].toArray()); } // create a Scholar.CSL instance var CSL = new Scholar.CSL(style); // return bibliography return CSL.createBibliography(itemArrays); } } /* * Scholar.CSL: a class for creating bibliographies from CSL files * this is abstracted as a separate class for the benefit of anyone who doesn't * want to use the Scholar data model, but does want to use CSL in JavaScript */ /* * constructor */ Scholar.CSL = function(csl) { default xml namespace = Scholar.CSL.ns; this._csl = new XML(csl); // load basic options this._parseOptions(); } Scholar.CSL._loc = { and:"and", etAl:"et al", pSingle:"p.", pMultiple:"pp.", editorVerb:"Edited By", editorNounSingle:"Ed.", editorNounMultiple:"Eds.", translatorVerb:"Translated By", translatorNounSingle:"Trans.", translatorNounMultiple:"Trans.", months:["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"], monthsAbbreviated:["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"], pagesShortSingle:"p", pagesShortMultiple:"pp", pagesLongSingle:"page", pagesLongMultiple:"pages" } Scholar.CSL._optionalTypeMappings = { journalArticle:"article-journal", magazineArticle:"article-magazine", newspaperArticle:"article-newspaper", thesis:"thesis", letter:"personal communication", manuscript:"manuscript", interview:"interview", film:"motion picture", artwork:"graphic", website:"webpage" }; // TODO: check with Elena/APA/MLA on this Scholar.CSL._fallbackTypeMappings = { book:"book", bookSection:"chapter", journalArticle:"article", magazineArticle:"article", newspaperArticle:"article", thesis:"book", letter:"article", manuscript:"book", interview:"book", film:"book", artwork:"book", website:"article" }; Scholar.CSL.ns = "http://purl.org/net/xbiblio/csl"; /* * create a bibliography * (items is expected to be an array of items) */ Scholar.CSL.prototype.createBibliography = function(items) { // sort by sort order if(this._opt.sortOrder == "author-date") { items.sort(function(a, b) { // first make sure both have creators at the first index if(!a.creators[0] && b.creators[0]) { return 1; } else if(!b.creators[0] && a.creators[0]) { return -1; } // now, either both have creators or neither do if(a.creators[0]) { // sort by last names if(b.creators[0].lastName > a.creators[0].lastName) { return 1; } else if(b.creators[0].lastName < a.creators[0].lastName) { return -1; } // sort by first name if(b.creators[0].firstName > a.creators[0].firstName) { return 1; } else if(b.creators[0].firstName < a.creators[0].firstName) { return -1; } } // now, sort by date var date1 = (a.date ? a.date : a.year); var date2 = (b.date ? b.date : b.year); if(date2 > date1) { return 1; } else if(date1 > date2) { return -1; } // finally, give up; they're the same return 0; }); } // process items var output = ""; for(var i in items) { var item = items[i]; if(item.itemType == "note") { // skip notes continue; } // determine mapping if(Scholar.CSL._optionalTypeMappings[item.itemType] && this._opt.referenceTypes[Scholar.CSL._optionalTypeMappings[item.itemType]]) { if(this._opt.referenceTypes[Scholar.CSL._optionalTypeMappings[item.itemType]] === true) { // exists but not yet processed this._parseReferenceType(Scholar.CSL._optionalTypeMappings[item.itemType]); } var reftype = this._opt.referenceTypes[Scholar.CSL._optionalTypeMappings[item.itemType]]; } else { if(this._opt.referenceTypes[Scholar.CSL._fallbackTypeMappings[item.itemType]] === true) { this._parseReferenceType(Scholar.CSL._fallbackTypeMappings[item.itemType]); } var reftype = this._opt.referenceTypes[Scholar.CSL._fallbackTypeMappings[item.itemType]]; } output += "

"+this._processElements(reftype, item)+"

\n"; } return output; } /* * process an item */ Scholar.CSL.prototype._processElements = function(reftype, item) { var output = ""; // separate item into authors, editors, translators var authors = new Array(); var editors = new Array(); var translators = new Array(); for(var j in item.creators) { if(item.creators[j].creatorType == "editor") { editors.push(item.creators[j]); } else if(item.creators[j].creatorType == "translator") { translators.push(item.creators[j]); } else { authors.push(item.creators[j]); } } if(item.date) { // specific date var date = this._processDate(item.date); } else { // no real date, but might salvage a year var date = new Object(); if(item.year) { date.year = item.year; } } for(var i in reftype) { var element = reftype[i]; var data = ""; if(element.name == "author") { if(authors.length) { data = this._processCreators("author", authors); } else if(element.alternate) { // no authors; use alternate if // it exists if(element.alternate == "editor") { data = this._processCreators("editor", editors); editors = new Array(); } else if(element.alternate == "title") { data = item.title; item.title = undefined; } else if(element.alternate == "container-title") { if(item.publication) { data = item.publication; item.publication = undefined; } } } } else if(element.name == "editor") { data = this._processCreators("editor", editors); } else if(element.name == "translator") { data = this._processCreators("translator", translators); } else if(element.name == "year") { data = date.year; } else if(element.name == "month-day") { data = date.month+" "+date.day; } else if(element.name == "date") { data = this._formatDate(date); } else if(element.name == "volume") { data = item.volume; } else if(element.name == "issue") { data = item.number; } else if(element.name == "pages") { if(item.pages) { if(this._opt.locators.label) { if(item.pages.indexOf(",") != -1 || item.pages.indexOf("-") != -1) { var label = this._opt.locators.label[1]; } else { var label = this._opt.locators.label[0]; } if(this._opt.locators.positionBefore) { data += label; } } data += item.pages; if(this._opt.locators.label && !this._opt.locators.positionBefore) { data += label; } } } else if(element.name == "title") { if(!element.type) { // standard title data = item.title; } else if(element.type == "container" && item.publication) { data = item.publication; } else if(element.type == "series") { data = item.series; } } else if(element.name == "publisher") { if(item.publisher) { if(item.place) { if(this._opt.publishers.publisherFirst) { data = item.publisher+this._opt.publishers.separator+item.place; } else { data = item.place+this._opt.publishers.separator+item.publisher; } } else { data = item.publisher; } } } else if(element.name == "access") { var dateAccessed = ""; if(item.dateAccessed) { var dateAccessed = this._formatDate(this._processDate(item.dateAccessed)); } if(this._opt.access.dateFirst) { data = (dateAccessed ? dateAccessed : ""); } else { data = (item.url ? item.url : ""); } if(dateAccessed && item.url) { data += this._opt.access.separator; } if(this._opt.access.dateFirst) { data += item.url; } else { data += dateAccessed; } } else if(element.name == "group") { data = this._processElements(element.elements, item); } else { data = element.name; } style = ""; var cssAttributes = ["font-family", "font-style", "font-variant", "font-weight", "text-transform"]; for(var j in cssAttributes) { if(element[cssAttributes[j]] && element[cssAttributes[j]].indexOf('"') == -1) { style += cssAttributes[j]+":"+element[cssAttributes[j]]; } } if(data) { var data = data.toString(); // add prefix if(element.prefix) { output += element.prefix; } if(style) { output += ''; } output += data; if(style) { output += ''; } if(element.suffix) { // suffix for this element only output += element.suffix; } else if(element.name != "group" && this._opt.suffix && data.substr(data.length-this._opt.suffix.length) != this._opt.suffix) { // global suffix if no suffix for this element output += this._opt.suffix; } } } return output; } /* * process creator objects; if someone had a creator model that handled * non-Western names better than ours, this would be the function to change */ Scholar.CSL.prototype._processCreators = function(type, creators) { var maxCreators = creators.length; if(!maxCreators) return; var useEtAl = false; // figure out if we need to use "et al" if(this._opt.etAl && maxCreators >= this._opt.etAl.minCreators) { maxCreators = this._opt.etAl.useFirst; useEtAl = true; } // parse authors into strings var authorStrings = []; var firstName, lastName; for(var i=0; i 1) { if(useEtAl) { // multiple creators and need et al authorStrings.push(Scholar.CSL._loc.etAl); } else { // multiple creators but no et al // add and to last creator authorStrings[maxCreators-1] = this._opt.names.and+" "+authorStrings[maxCreators-1]; // skip the comma if there are only two creators and no // et al if(maxCreators == 2) { joinString = " "; } } } var returnString = authorStrings.join(joinString); // add "Edited By" or "Translated By" if(this._opt.contributors.label[type]) { // figure out whether to use singular or plural representation if(maxCreators == 1) { var label = this._opt.contributors.label[type][0]; } else { var label = this._opt.contributors.label[type][1]; } // figure out where to add if(this._opt.contributors.positionBefore) { returnString = label+" "+returnString; } else { returnString += " ("+label+")"; } } // add to the data return returnString; } /* * process the date "string" into a useful object */ Scholar.CSL.prototype._processDate = function(string) { var date = new Object(); var dateRe = /^([0-9]{4})-([0-9]{2})-([0-9]{2})$/; var m = dateRe.exec(string); if(m) { // sql date var jsDate = new Date(m[1], m[2]-1, m[3], false, false, false); } else { // not an sql date var jsDate = new Date(string); } if(isNaN(jsDate.valueOf())) { // couldn't parse // get year and say other parts are month var yearRe = /^(.*)([^0-9]{4})(.*)$/ var m = yearRe.exec(string); date.year = m[2]; date.month = m[1] if(m[2] && m[3]) date.month += " "; date.month += m[3]; } else { date.year = jsDate.getFullYear(); date.month = this._opt.dates.months[jsDate.getMonth()]; date.day = jsDate.getDay(); } return date; } /* * format the date according to date processing preference from the date object * returned by this._processDate */ Scholar.CSL.prototype._formatDate = function(date) { var data = this._opt.dates.format.replace("year", (date.year ? date.year : "")); data = data.replace("month", (date.month ? date.month : "")); data = data.replace("day", (date.day ? date.day : "")); data = data.replace(/^[^\w]+/, ""); data = data.replace(/[^\w]+$/, ""); return data; } default xml namespace = Scholar.CSL.ns; /* * convert options to native structures for speed */ Scholar.CSL.prototype._parseOptions = function() { default xml namespace = Scholar.CSL.ns; this._opt = new Object(); // names this._opt.names = new Object(); if(this._csl.general.names.@and == "text") { this._opt.names.and = Scholar.CSL._loc.and; } else if(this._csl.general.names.@and == "symbol") { this._opt.names.and = "&"; } else { this._opt.names.and = ""; } this._opt.names.sortSeparator = this._csl.general.names["@sort-separator"].toString(); this._opt.names.initializeWith = this._csl.general.names["@initialize-with"].toString(); if(this._csl.bibliography["@author-as-sort-order"] == "all") { this._opt.names.firstAuthorInverted = true; this._opt.names.subsequentAuthorInverted = true; } else if(this._csl.bibliography["@author-as-sort-order"] == "first-author") { this._opt.names.firstAuthorInverted = true; this._opt.names.subsequentAuthorInverted = false; } else { this._opt.names.firstAuthorInverted = false; this._opt.names.subsequentAuthorInverted = false; } // contributors this._opt.contributors = new Object(); if(this._csl.general.contributors.label.length) { // contributors if(this._csl.general.contributors.label.@position == "before") { this._opt.contributors.positionBefore = true; } if(this._csl.general.contributors.label.@type == "verb") { this._opt.contributors.label = {editor:[Scholar.CSL._loc.editorVerb, Scholar.CSL._loc.editorVerb], translator:[Scholar.CSL._loc.translatorVerb, Scholar.CSL._loc.translatorVerb]} } else { this._opt.contributors.label = {editor:[Scholar.CSL._loc.editorNounSingle, Scholar.CSL._loc.editorNounMultiple], translator:[Scholar.CSL._loc.translatorNounSingle, Scholar.CSL._loc.translatorNounMultiple]} } } // locators this._opt.locators = new Object(); if(this._csl.general.Scholar.CSL._locators.label.length) { // contributors if(this._csl.general.Scholar.CSL._locators.label.@position == "before") { this._opt.locators.positionBefore = true; } if(this._csl.general.Scholar.CSL._locators.label.@form == "short") { this._opt.locators.label = [Scholar.CSL._loc.pagesShortSingle, Scholar.CSL._loc.pagesShortMultiple]; } else { this._opt.locators.label = [Scholar.CSL._loc.pagesLongSingle, Scholar.CSL._loc.pagesLongMultiple]; } } // dates this._opt.dates = new Object(); this._opt.dates.format = this._csl.general.dates.@format.toString(); if(this._csl.general.dates.@month == "abbreviated") { this._opt.dates.months = Scholar.CSL._loc.monthsAbbreviated; } else { this._opt.dates.months = Scholar.CSL._loc.months; } // publishers this._opt.publishers = new Object(); if(this._csl.general.publishers.@order == "publisher-address") { this._opt.publishers.publisherFirst = true; } this._opt.publishers.separator = this._csl.general.publishers.@separator.toString(); // access this._opt.access = new Object(); if(this._csl.general.access.@order == "date-url") { this._opt.access.dateFirst = true; } this._opt.access.separator = this._csl.general.access.@separator.toString(); // et al if(this._csl.bibliography['use-et_al'].length()) { this._opt.names.etAl = new Object(); this._opt.names.etAl.minCreators = parseInt(this._csl.bibliography['use-et_al']['@min-authors']); this._opt.names.etAl.useFirst = parseInt(this._csl.bibliography['use-et_al']['@use-first']); } // sort order this._opt.sortOrder = this._csl.bibliography["@sort-order"].toString(); // referenceTypes this._opt.referenceTypes = new Object(); for each(var element in this._csl.bibliography['item-layout'].reftype) { if(element.namespace() == Scholar.CSL.ns) { // ignore elements in other namespaces this._opt.referenceTypes[element.@name.toString()] = true; } } // global prefix and suffix this._opt.suffix = this._csl.bibliography["item-layout"].@suffix.toString(); this._opt.prefix = this._csl.bibliography["item-layout"].@prefix.toString(); } /* * does the dirty work for parseReferenceTypes - recursively process attributes * into an associative array */ Scholar.CSL.prototype._parseElements = function(ref) { var typeDesc = new Array(); for each(var element in ref) { if(element.namespace() == Scholar.CSL.ns) { // ignore elements in other namespaces var itemDesc = new Object(); itemDesc.name = element.localName(); var attributes = element.attributes(); for each(var attribute in attributes) { itemDesc[attribute.name()] = attribute.toString(); } if(itemDesc.name == "group") { // parse groups recursively itemDesc.elements = this._parseElements(element.elements()); } typeDesc.push(itemDesc); } } return typeDesc; } /* * convert reference types to native structures for speed */ Scholar.CSL.prototype._parseReferenceType = function(reftype) { default xml namespace = Scholar.CSL.ns; var ref = this._csl.bibliography['item-layout'].reftype.(@name==reftype).elements(); this._opt.referenceTypes[reftype] = this._parseElements(ref); }