www

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | Submodules | README | LICENSE

commit de2d1669fea84116028f40c728f84d655ac33fdd
parent 0deb2573cc841494f58341754527409cf8f39a0c
Author: Simon Kornblith <simon@simonster.com>
Date:   Sat, 14 Jul 2012 22:26:29 -0400

Restructure and comment cite.js

Diffstat:
Mchrome/content/zotero/xpcom/cite.js | 827+++++++++++++++++++++++++++++++++++++++++--------------------------------------
1 file changed, 433 insertions(+), 394 deletions(-)

diff --git a/chrome/content/zotero/xpcom/cite.js b/chrome/content/zotero/xpcom/cite.js @@ -1,442 +1,481 @@ -Zotero.Cite = function(){} -Zotero.Cite.System = function(){}; - -Zotero.Cite.System._quotedRegexp = /^".+"$/; - -// TODO: Clear this cache from time to time -Zotero.Cite.System._cache = new Object(); - -Zotero.Cite.System.retrieveItem = function(item) { - var zoteroItem, slashIndex; - if(item instanceof Zotero.Item) { - //if(this._cache[item.id]) return this._cache[item.id]; - zoteroItem = item; - } else { - var type = typeof item; - if(type === "string" && (slashIndex = item.indexOf("/")) !== -1) { - // is an embedded item - var sessionID = item.substr(0, slashIndex); - var session = Zotero.Integration.sessions[sessionID] - if(session) { - var embeddedCitation = session.embeddedItems[item.substr(slashIndex+1)]; - if(embeddedCitation) { - embeddedCitation.id = item; - return embeddedCitation; +/** + * Utility functions for dealing with citations + * @namespace + */ +Zotero.Cite = { + /** + * Locator labels + */ + "labels":["page", "book", "chapter", "column", "figure", "folio", + "issue", "line", "note", "opus", "paragraph", "part", "section", "sub verbo", + "volume", "verse"], + + /** + * Remove specified item IDs in-place from a citeproc-js bibliography object returned + * by makeBibliography() + * @param {bib} citeproc-js bibliography object + * @param {Array} itemsToRemove Array of items to remove + */ + "removeFromBibliography":function(bib, itemsToRemove) { + var removeItems = []; + for(let i in bib[0].entry_ids) { + for(let j in bib[0].entry_ids[i]) { + if(itemsToRemove[bib[0].entry_ids[i][j]]) { + removeItems.push(i); + break; } } - } else { - // is an item ID - //if(this._cache[item]) return this._cache[item]; - zoteroItem = Zotero.Items.get(item); } - } + for(let i=removeItems.length-1; i>=0; i--) { + bib[0].entry_ids.splice(removeItems[i], 1); + bib[1].splice(removeItems[i], 1); + } + }, - if(!zoteroItem) { - throw "Zotero.Cite.getCSLItem called to wrap a non-item "+item; - } - - // don't return URL or accessed information for journal articles if a - // pages field exists - var itemType = Zotero.ItemTypes.getName(zoteroItem.itemTypeID); - var cslType = CSL_TYPE_MAPPINGS[itemType]; - if(!cslType) cslType = "article"; - var ignoreURL = ((zoteroItem.getField("accessDate", true, true) || zoteroItem.getField("url", true, true)) && - ["journalArticle", "newspaperArticle", "magazineArticle"].indexOf(itemType) !== -1 - && zoteroItem.getField("pages") - && !Zotero.Prefs.get("export.citePaperJournalArticleURL")); - - var cslItem = { - 'id':zoteroItem.id, - 'type':cslType - }; - - // get all text variables (there must be a better way) - // TODO: does citeproc-js permit short forms? - for(var variable in CSL_TEXT_MAPPINGS) { - var fields = CSL_TEXT_MAPPINGS[variable]; - if(variable == "URL" && ignoreURL) continue; - for each(var field in fields) { - var value = zoteroItem.getField(field, false, true).toString(); - if(value != "") { - // Strip enclosing quotes - if(value.match(Zotero.Cite.System._quotedRegexp)) { - value = value.substr(1, value.length-2); - } - cslItem[variable] = value; - break; + /** + * Convert formatting data from citeproc-js bibliography object into explicit format + * parameters for RTF or word processors + * @param {bib} citeproc-js bibliography object + * @return {Object} Bibliography style parameters. + */ + "getBibliographyFormatParameters":function getBibliographyFormatParameters(bib) { + var bibStyle = {"tabStops":[], "indent":0, "firstLineIndent":0, + "lineSpacing":(240*bib[0].linespacing), + "entrySpacing":(240*bib[0].entryspacing)}; + if(bib[0].hangingindent) { + bibStyle.indent = 720; // 720 twips = 0.5 in + bibStyle.firstLineIndent = -720; // -720 twips = -0.5 in + } else if(bib[0]["second-field-align"]) { + // this is a really sticky issue. the below works for first fields that look like "[1]" + // and "1." otherwise, i have no idea. luckily, this will be good enough 99% of the time. + var alignAt = 24+bib[0].maxoffset*120; + bibStyle.firstLineIndent = -alignAt; + if(bib[0]["second-field-align"] == "margin") { + bibStyle.tabStops = [0]; + } else { + bibStyle.indent = alignAt; + bibStyle.tabStops = [alignAt]; } } - } - - // separate name variables - var authorID = Zotero.CreatorTypes.getPrimaryIDForType(zoteroItem.itemTypeID); - var creators = zoteroItem.getCreators(); - for each(var creator in creators) { - if(creator.creatorTypeID == authorID) { - var creatorType = "author"; - } else { - var creatorType = Zotero.CreatorTypes.getName(creator.creatorTypeID); - } - var creatorType = CSL_NAMES_MAPPINGS[creatorType]; - if(!creatorType) continue; + return bibStyle; + }, + + /** + * Makes a formatted bibliography, if the style defines one; otherwise makes a + * formatted list of items + * @param {Zotero.Style} style The style to use + * @param {Zotero.Item[]} items An array of items + * @param {String} format The format of the output (html, text, or rtf) + * @return {String} Bibliography or item list in specified format + */ + "makeFormattedBibliographyOrCitationList":function(style, items, format) { + var cslEngine = style.csl; + cslEngine.setOutputFormat(format); + cslEngine.updateItems([item.id for each(item in items)]); - var nameObj = {'family':creator.ref.lastName, 'given':creator.ref.firstName}; + var bibliography = Zotero.Cite.makeFormattedBibliography(cslEngine, format); + if(bibliography) return bibliography; - if(cslItem[creatorType]) { - cslItem[creatorType].push(nameObj); - } else { - cslItem[creatorType] = [nameObj]; - } - } - - // get date variables - for(var variable in CSL_DATE_MAPPINGS) { - var date = zoteroItem.getField(CSL_DATE_MAPPINGS[variable], false, true); - if(date) { - var dateObj = Zotero.Date.strToDate(date); - // otherwise, use date-parts - var dateParts = []; - if(dateObj.year) { - // add year, month, and day, if they exist - dateParts.push(dateObj.year); - if(dateObj.month !== undefined) { - dateParts.push(dateObj.month+1); - if(dateObj.day) { - dateParts.push(dateObj.day); - } + var styleClass = style.class; + var citations = [cslEngine.appendCitationCluster({"citationItems":[{"id":item.id}], "properties":{}}, true)[0][1] + for each(item in items)]; + + if(styleClass == "note") { + if(format == "html") { + return "<ol>\n\t<li>"+citations.join("</li>\n\t<li>")+"</li>\n</ol>"; + } else if(format == "text") { + var output = []; + for(var i=0; i<citations.length; i++) { + output.push((i+1)+". "+citations[i]+"\r\n"); } - cslItem[variable] = {"date-parts":[dateParts]}; - - // if no month, use season as month - if(dateObj.part && !dateObj.month) { - cslItem[variable].season = dateObj.part; + return output.join(""); + } else if(format == "rtf") { + var output = ["{\\rtf \n{\\*\\listtable{\\list\\listtemplateid1\\listhybrid{\\listlevel"+ + "\\levelnfc0\\levelnfcn0\\leveljc0\\leveljcn0\\levelfollow0\\levelstartat1"+ + "\\levelspace360\\levelindent0{\\*\\levelmarker \\{decimal\\}.}{\\leveltext"+ + "\\leveltemplateid1\\'02\\'00.;}{\\levelnumbers\\'01;}\\fi-360\\li720\\lin720 }"+ + "{\\listname ;}\\listid1}}\n{\\*\\listoverridetable{\\listoverride\\listid1"+ + "\\listoverridecount0\\ls1}}\n\\tx720\\li720\\fi-480\\ls1\\ilvl0\n"]; + for(var i=0; i<citations.length; i++) { + output.push("{\\listtext "+(i+1)+". }"+citations[i]+"\\\n"); } + output.push("}"); + return output.join(""); } else { - // if no year, pass date literally - cslItem[variable] = {"literal":date}; - } - } - } - - //this._cache[zoteroItem.id] = cslItem; - return cslItem; -}; - -Zotero.Cite.System.retrieveLocale = function(lang) { - var protHandler = Components.classes["@mozilla.org/network/protocol;1?name=chrome"] - .createInstance(Components.interfaces.nsIProtocolHandler); - try { - var channel = protHandler.newChannel(protHandler.newURI("chrome://zotero/content/locale/csl/locales-"+lang+".xml", "UTF-8", null)); - var rawStream = channel.open(); - } catch(e) { - return false; - } - var converterStream = Components.classes["@mozilla.org/intl/converter-input-stream;1"] - .createInstance(Components.interfaces.nsIConverterInputStream); - converterStream.init(rawStream, "UTF-8", 65535, - Components.interfaces.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER); - var str = {}; - converterStream.readString(channel.contentLength, str); - converterStream.close(); - return str.value; -}; - -Zotero.Cite.System.getAbbreviations = function() { - return {}; -} - -Zotero.Cite.removeFromBibliography = function(bib, itemsToRemove) { - var removeItems = []; - for(let i in bib[0].entry_ids) { - for(let j in bib[0].entry_ids[i]) { - if(itemsToRemove[bib[0].entry_ids[i][j]]) { - removeItems.push(i); - break; + throw "Unimplemented bibliography format "+format; } - } - } - for(let i=removeItems.length-1; i>=0; i--) { - bib[0].entry_ids.splice(removeItems[i], 1); - bib[1].splice(removeItems[i], 1); - } -} - -Zotero.Cite.getBibliographyFormatParameters = function(bib) { - var bibStyle = {"tabStops":[], "indent":0, "firstLineIndent":0, - "lineSpacing":(240*bib[0].linespacing), - "entrySpacing":(240*bib[0].entryspacing)}; - if(bib[0].hangingindent) { - bibStyle.indent = 720; // 720 twips = 0.5 in - bibStyle.firstLineIndent = -720; // -720 twips = -0.5 in - } else if(bib[0]["second-field-align"]) { - // this is a really sticky issue. the below works for first fields that look like "[1]" - // and "1." otherwise, i have no idea. luckily, this will be good enough 99% of the time. - var alignAt = 24+bib[0].maxoffset*120; - bibStyle.firstLineIndent = -alignAt; - if(bib[0]["second-field-align"] == "margin") { - bibStyle.tabStops = [0]; } else { - bibStyle.indent = alignAt; - bibStyle.tabStops = [alignAt]; - } - } - - return bibStyle; -} - -/** - * Makes a formatted bibliography, if the style defines one; otherwise makes a formatted list of - * items - * @param {Zotero.Style} style The style to use - * @param {Zotero.Item[]} items An array of items - * @param {String} format The format of the output - */ -Zotero.Cite.makeFormattedBibliographyOrCitationList = function(style, items, format) { - var cslEngine = style.csl; - cslEngine.setOutputFormat(format); - cslEngine.updateItems([item.id for each(item in items)]); - - var bibliography = Zotero.Cite.makeFormattedBibliography(cslEngine, format); - if(bibliography) return bibliography; - - var styleClass = style.class; - var citations = [cslEngine.appendCitationCluster({"citationItems":[{"id":item.id}], "properties":{}}, true)[0][1] - for each(item in items)]; - - if(styleClass == "note") { - if(format == "html") { - return "<ol>\n\t<li>"+citations.join("</li>\n\t<li>")+"</li>\n</ol>"; - } else if(format == "text") { - var output = []; - for(var i=0; i<citations.length; i++) { - output.push((i+1)+". "+citations[i]+"\r\n"); + if(format == "html") { + return citations.join("<br />"); + } else if(format == "text") { + return citations.join("\r\n"); + } else if(format == "rtf") { + return "<\\rtf \n"+citations.join("\\\n")+"\n}"; } - return output.join(""); - } else if(format == "rtf") { - var output = ["{\\rtf \n{\\*\\listtable{\\list\\listtemplateid1\\listhybrid{\\listlevel"+ - "\\levelnfc0\\levelnfcn0\\leveljc0\\leveljcn0\\levelfollow0\\levelstartat1"+ - "\\levelspace360\\levelindent0{\\*\\levelmarker \\{decimal\\}.}{\\leveltext"+ - "\\leveltemplateid1\\'02\\'00.;}{\\levelnumbers\\'01;}\\fi-360\\li720\\lin720 }"+ - "{\\listname ;}\\listid1}}\n{\\*\\listoverridetable{\\listoverride\\listid1"+ - "\\listoverridecount0\\ls1}}\n\\tx720\\li720\\fi-480\\ls1\\ilvl0\n"]; - for(var i=0; i<citations.length; i++) { - output.push("{\\listtext "+(i+1)+". }"+citations[i]+"\\\n"); - } - output.push("}"); - return output.join(""); - } else { - throw "Unimplemented bibliography format "+format; } - } else { - if(format == "html") { - return citations.join("<br />"); - } else if(format == "text") { - return citations.join("\r\n"); - } else if(format == "rtf") { - return "<\\rtf \n"+citations.join("\\\n")+"\n}"; - } - } -} - -/** - * Makes a formatted bibliography - * @param {Zotero.Style} style The style - * @param {Zotero.Item[]} items An array of items - */ -Zotero.Cite.makeFormattedBibliography = function(cslEngine, format) { - cslEngine.setOutputFormat(format); - var bib = cslEngine.makeBibliography(); - if(!bib) return false; + }, - if(format == "html") { - var output = [bib[0].bibstart]; - for(var i in bib[1]) { - output.push(bib[1][i]); - - // add COinS - for each(var itemID in bib[0].entry_ids[i]) { - try { - var co = Zotero.OpenURL.createContextObject(Zotero.Items.get(itemID), "1.0"); - if(!co) continue; - output.push(' <span class="Z3988" title="'+ - co.replace("&", "&amp;", "g").replace("<", "&lt;", "g").replace(">", "&gt;", "g")+ - '"/>\n'); - } catch(e) { - Zotero.logError(e); + /** + * Makes a formatted bibliography + * @param {Zotero.Style} style The style + * @param {String} format The format of the output (html, text, or rtf) + * @return {String} Bibliography in specified format + */ + "makeFormattedBibliography":function makeFormattedBibliography(cslEngine, format) { + cslEngine.setOutputFormat(format); + var bib = cslEngine.makeBibliography(); + if(!bib) return false; + + if(format == "html") { + var output = [bib[0].bibstart]; + for(var i in bib[1]) { + output.push(bib[1][i]); + + // add COinS + for each(var itemID in bib[0].entry_ids[i]) { + try { + var co = Zotero.OpenURL.createContextObject(Zotero.Items.get(itemID), "1.0"); + if(!co) continue; + output.push(' <span class="Z3988" title="'+ + co.replace("&", "&amp;", "g").replace("<", "&lt;", "g").replace(">", "&gt;", "g")+ + '"/>\n'); + } catch(e) { + Zotero.logError(e); + } } } - } - output.push(bib[0].bibend); - var html = output.join(""); - - var inlineCSS = true; - if (!inlineCSS) { - return html; - } - - //Zotero.debug("maxoffset: " + bib[0].maxoffset); - //Zotero.debug("entryspacing: " + bib[0].entryspacing); - //Zotero.debug("linespacing: " + bib[0].linespacing); - //Zotero.debug("hangingindent: " + bib[0].hangingindent); - //Zotero.debug("second-field-align: " + bib[0]["second-field-align"]); - - var maxOffset = parseInt(bib[0].maxoffset); - var entrySpacing = parseInt(bib[0].entryspacing); - var lineSpacing = parseInt(bib[0].linespacing); - var hangingIndent = parseInt(bib[0].hangingindent); - var secondFieldAlign = bib[0]["second-field-align"]; - - // Validate input - if(maxOffset == NaN) throw "Invalid maxoffset"; - if(entrySpacing == NaN) throw "Invalid entryspacing"; - if(lineSpacing == NaN) throw "Invalid linespacing"; - - var str; - try { - var parser = Components.classes["@mozilla.org/xmlextras/domparser;1"] - .createInstance(Components.interfaces.nsIDOMParser), - doc = parser.parseFromString(html, "application/xml"); - - var leftMarginDivs = Zotero.Utilities.xpath(doc, '//div[@class="csl-left-margin"]'), - multiField = !!leftMarginDivs.length, - clearEntries = multiField; + output.push(bib[0].bibend); + var html = output.join(""); - // One of the characters is usually a period, so we can adjust this down a bit - maxOffset = Math.max(1, maxOffset - 2); - - // Force a minimum line height - if(lineSpacing <= 1.35) lineSpacing = 1.35; + var inlineCSS = true; + if (!inlineCSS) { + return html; + } - var style = doc.documentElement.getAttribute("style"); - if(!style) style = ""; - style += "line-height: " + lineSpacing + "; "; + //Zotero.debug("maxoffset: " + bib[0].maxoffset); + //Zotero.debug("entryspacing: " + bib[0].entryspacing); + //Zotero.debug("linespacing: " + bib[0].linespacing); + //Zotero.debug("hangingindent: " + bib[0].hangingindent); + //Zotero.debug("second-field-align: " + bib[0]["second-field-align"]); - if(hangingIndent) { - if (multiField && !secondFieldAlign) { - throw ("second-field-align=false and hangingindent=true combination is not currently supported"); - } - // If only one field, apply hanging indent on root - else if (!multiField) { - style += "padding-left: " + hangingIndent + "em; text-indent:-" + hangingIndent + "em;"; - } - } + var maxOffset = parseInt(bib[0].maxoffset); + var entrySpacing = parseInt(bib[0].entryspacing); + var lineSpacing = parseInt(bib[0].linespacing); + var hangingIndent = parseInt(bib[0].hangingindent); + var secondFieldAlign = bib[0]["second-field-align"]; - if(style) doc.documentElement.setAttribute("style", style); + // Validate input + if(maxOffset == NaN) throw "Invalid maxoffset"; + if(entrySpacing == NaN) throw "Invalid entryspacing"; + if(lineSpacing == NaN) throw "Invalid linespacing"; - // csl-entry - var divs = Zotero.Utilities.xpath(doc, '//div[@class="csl-entry"]'); - for(var i=0, n=divs.length; i<n; i++) { - var div = divs[i], - divStyle = div.getAttribute("style"); - if(!divStyle) divStyle = ""; + var str; + try { + var parser = Components.classes["@mozilla.org/xmlextras/domparser;1"] + .createInstance(Components.interfaces.nsIDOMParser), + doc = parser.parseFromString(html, "application/xml"); - if (clearEntries) { - divStyle += "clear: left; "; - } + var leftMarginDivs = Zotero.Utilities.xpath(doc, '//div[@class="csl-left-margin"]'), + multiField = !!leftMarginDivs.length, + clearEntries = multiField; - if(entrySpacing && i !== n - 1) { - divStyle += "margin-bottom: " + entrySpacing + "em;"; - } + // One of the characters is usually a period, so we can adjust this down a bit + maxOffset = Math.max(1, maxOffset - 2); - if(divStyle) div.setAttribute("style", divStyle); - } - - // Padding on the label column, which we need to include when - // calculating offset of right column - var rightPadding = .5; - - // div.csl-left-margin - for each(var div in leftMarginDivs) { - var divStyle = div.getAttribute("style"); - if(!divStyle) divStyle = ""; + // Force a minimum line height + if(lineSpacing <= 1.35) lineSpacing = 1.35; - divStyle = "float: left; padding-right: " + rightPadding + "em;"; + var style = doc.documentElement.getAttribute("style"); + if(!style) style = ""; + style += "line-height: " + lineSpacing + "; "; - // Right-align the labels if aligning second line, since it looks - // better and we don't need the second line of text to align with - // the left edge of the label - if (secondFieldAlign) { - divStyle += "text-align: right; width: " + maxOffset + "em;"; + if(hangingIndent) { + if (multiField && !secondFieldAlign) { + throw ("second-field-align=false and hangingindent=true combination is not currently supported"); + } + // If only one field, apply hanging indent on root + else if (!multiField) { + style += "padding-left: " + hangingIndent + "em; text-indent:-" + hangingIndent + "em;"; + } } - div.setAttribute("style", divStyle); - } - - // div.csl-right-inline - for each(var div in Zotero.Utilities.xpath(doc, '//div[@class="csl-right-inline"]')) { - var divStyle = div.getAttribute("style"); - if(!divStyle) divStyle = ""; + if(style) doc.documentElement.setAttribute("style", style); + + // csl-entry + var divs = Zotero.Utilities.xpath(doc, '//div[@class="csl-entry"]'); + for(var i=0, n=divs.length; i<n; i++) { + var div = divs[i], + divStyle = div.getAttribute("style"); + if(!divStyle) divStyle = ""; + + if (clearEntries) { + divStyle += "clear: left; "; + } + + if(entrySpacing && i !== n - 1) { + divStyle += "margin-bottom: " + entrySpacing + "em;"; + } + + if(divStyle) div.setAttribute("style", divStyle); + } + + // Padding on the label column, which we need to include when + // calculating offset of right column + var rightPadding = .5; + + // div.csl-left-margin + for each(var div in leftMarginDivs) { + var divStyle = div.getAttribute("style"); + if(!divStyle) divStyle = ""; + + divStyle = "float: left; padding-right: " + rightPadding + "em;"; + + // Right-align the labels if aligning second line, since it looks + // better and we don't need the second line of text to align with + // the left edge of the label + if (secondFieldAlign) { + divStyle += "text-align: right; width: " + maxOffset + "em;"; + } + + div.setAttribute("style", divStyle); + } - divStyle = "margin: 0 .4em 0 " + (secondFieldAlign ? maxOffset + rightPadding : "0") + "em;"; + // div.csl-right-inline + for each(var div in Zotero.Utilities.xpath(doc, '//div[@class="csl-right-inline"]')) { + var divStyle = div.getAttribute("style"); + if(!divStyle) divStyle = ""; + + divStyle = "margin: 0 .4em 0 " + (secondFieldAlign ? maxOffset + rightPadding : "0") + "em;"; + + if (hangingIndent) { + divSstyle += "padding-left: " + hangingIndent + "em; text-indent:-" + hangingIndent + "em;"; + } + + div.setAttribute("style", divStyle); + } - if (hangingIndent) { - divSstyle += "padding-left: " + hangingIndent + "em; text-indent:-" + hangingIndent + "em;"; + // div.csl-indent + for each(var div in Zotero.Utilities.xpath(doc, '//div[@class="csl-indent"]')) { + div.setAttribute("style", "margin: .5em 0 0 2em; padding: 0 0 .2em .5em; border-left: 5px solid #ccc;"); } - div.setAttribute("style", divStyle); + //Zotero.debug(xml); + var s = Components.classes["@mozilla.org/xmlextras/xmlserializer;1"] + .createInstance(Components.interfaces.nsIDOMSerializer); + str = s.serializeToString(doc); + } finally { + XML.prettyPrinting = true; + XML.ignoreWhitespace = true; } - // div.csl-indent - for each(var div in Zotero.Utilities.xpath(doc, '//div[@class="csl-indent"]')) { - div.setAttribute("style", "margin: .5em 0 0 2em; padding: 0 0 .2em .5em; border-left: 5px solid #ccc;"); - } + return str; + } else if(format == "text") { + return bib[0].bibstart+bib[1].join("")+bib[0].bibend; + } else if(format == "rtf") { + var bibStyle = Zotero.Cite.getBibliographyFormatParameters(bib); + + var preamble = (bibStyle.tabStops.length ? "\\tx"+bibStyle.tabStops.join(" \\tx")+" " : ""); + preamble += "\\li"+bibStyle.indent+" \\fi"+bibStyle.firstLineIndent+" " + +"\\sl"+bibStyle.lineSpacing+" \\slmult1 " + +"\\sa"+bibStyle.entrySpacing+" "; - //Zotero.debug(xml); - var s = Components.classes["@mozilla.org/xmlextras/xmlserializer;1"] - .createInstance(Components.interfaces.nsIDOMSerializer); - str = s.serializeToString(doc); - } finally { - XML.prettyPrinting = true; - XML.ignoreWhitespace = true; + return bib[0].bibstart+preamble+bib[1].join("\\\r\n")+"\\\r\n"+bib[0].bibend; + } else { + throw "Unimplemented bibliography format "+format; } + }, + + /** + * Get an item by ID, either by retrieving it from the library or looking for the document it + * belongs to. + * @param {String|Number|Array} id + * @return {Zotero.Item} item + */ + "getItem":function getItem(id) { + var slashIndex; - return str; - } else if(format == "text") { - return bib[0].bibstart+bib[1].join("")+bib[0].bibend; - } else if(format == "rtf") { - var bibStyle = Zotero.Cite.getBibliographyFormatParameters(bib); - - var preamble = (bibStyle.tabStops.length ? "\\tx"+bibStyle.tabStops.join(" \\tx")+" " : ""); - preamble += "\\li"+bibStyle.indent+" \\fi"+bibStyle.firstLineIndent+" " - +"\\sl"+bibStyle.lineSpacing+" \\slmult1 " - +"\\sa"+bibStyle.entrySpacing+" "; - - return bib[0].bibstart+preamble+bib[1].join("\\\r\n")+"\\\r\n"+bib[0].bibend; - } else { - throw "Unimplemented bibliography format "+format; + if(id instanceof Array) { + return [Zotero.Cite.getItem(anId) for each(anId in id)]; + } else if(typeof id === "string" && (slashIndex = id.indexOf("/")) !== -1) { + var sessionID = id.substr(0, slashIndex), + session = Zotero.Integration.sessions[sessionID], + item; + if(session) { + item = session.embeddedZoteroItems[id.substr(slashIndex+1)]; + } + + if(!item) { + item = new Zotero.Item("document"); + item.setField("title", "Missing Item"); + Zotero.log("CSL item "+id+" not found"); + } + return item; + } else { + return Zotero.Items.get(id); + } } -} +}; /** - * Get an item by ID, either by retrieving it from the library or looking for the document it - * belongs to. - * @param {String|Number|Array} id + * citeproc-js system object + * @namespace */ -Zotero.Cite.getItem = function(id) { - var slashIndex; +Zotero.Cite.System = { + /** + * citeproc-js system function for getting items + * See http://gsl-nagoya-u.net/http/pub/citeproc-doc.html#retrieveitem + * @param {String|Integer} Item ID, or string item for embedded citations + * @return {Object} citeproc-js item + */ + "retrieveItem":function retrieveItem(item) { + var zoteroItem, slashIndex; + if(item instanceof Zotero.Item) { + //if(this._cache[item.id]) return this._cache[item.id]; + zoteroItem = item; + } else { + var type = typeof item; + if(type === "string" && (slashIndex = item.indexOf("/")) !== -1) { + // is an embedded item + var sessionID = item.substr(0, slashIndex); + var session = Zotero.Integration.sessions[sessionID] + if(session) { + var embeddedCitation = session.embeddedItems[item.substr(slashIndex+1)]; + if(embeddedCitation) { + embeddedCitation.id = item; + return embeddedCitation; + } + } + } else { + // is an item ID + //if(this._cache[item]) return this._cache[item]; + zoteroItem = Zotero.Items.get(item); + } + } - if(id instanceof Array) { - return [Zotero.Cite.getItem(anId) for each(anId in id)]; - } else if(typeof id === "string" && (slashIndex = id.indexOf("/")) !== -1) { - var sessionID = id.substr(0, slashIndex), - session = Zotero.Integration.sessions[sessionID], - item; - if(session) { - item = session.embeddedZoteroItems[id.substr(slashIndex+1)]; + if(!zoteroItem) { + throw "Zotero.Cite.getCSLItem called to wrap a non-item "+item; } - if(!item) { - item = new Zotero.Item("document"); - item.setField("title", "Missing Item"); - Zotero.log("CSL item "+id+" not found"); + // don't return URL or accessed information for journal articles if a + // pages field exists + var itemType = Zotero.ItemTypes.getName(zoteroItem.itemTypeID); + var cslType = CSL_TYPE_MAPPINGS[itemType]; + if(!cslType) cslType = "article"; + var ignoreURL = ((zoteroItem.getField("accessDate", true, true) || zoteroItem.getField("url", true, true)) && + ["journalArticle", "newspaperArticle", "magazineArticle"].indexOf(itemType) !== -1 + && zoteroItem.getField("pages") + && !Zotero.Prefs.get("export.citePaperJournalArticleURL")); + + var cslItem = { + 'id':zoteroItem.id, + 'type':cslType + }; + + // get all text variables (there must be a better way) + // TODO: does citeproc-js permit short forms? + for(var variable in CSL_TEXT_MAPPINGS) { + var fields = CSL_TEXT_MAPPINGS[variable]; + if(variable == "URL" && ignoreURL) continue; + for each(var field in fields) { + var value = zoteroItem.getField(field, false, true).toString(); + if(value != "") { + // Strip enclosing quotes + if(value.match(/^".+"$/)) { + value = value.substr(1, value.length-2); + } + cslItem[variable] = value; + break; + } + } } - return item; - } else { - return Zotero.Items.get(id); - } -} + + // separate name variables + var authorID = Zotero.CreatorTypes.getPrimaryIDForType(zoteroItem.itemTypeID); + var creators = zoteroItem.getCreators(); + for each(var creator in creators) { + if(creator.creatorTypeID == authorID) { + var creatorType = "author"; + } else { + var creatorType = Zotero.CreatorTypes.getName(creator.creatorTypeID); + } + + var creatorType = CSL_NAMES_MAPPINGS[creatorType]; + if(!creatorType) continue; + + var nameObj = {'family':creator.ref.lastName, 'given':creator.ref.firstName}; + + if(cslItem[creatorType]) { + cslItem[creatorType].push(nameObj); + } else { + cslItem[creatorType] = [nameObj]; + } + } + + // get date variables + for(var variable in CSL_DATE_MAPPINGS) { + var date = zoteroItem.getField(CSL_DATE_MAPPINGS[variable], false, true); + if(date) { + var dateObj = Zotero.Date.strToDate(date); + // otherwise, use date-parts + var dateParts = []; + if(dateObj.year) { + // add year, month, and day, if they exist + dateParts.push(dateObj.year); + if(dateObj.month !== undefined) { + dateParts.push(dateObj.month+1); + if(dateObj.day) { + dateParts.push(dateObj.day); + } + } + cslItem[variable] = {"date-parts":[dateParts]}; + + // if no month, use season as month + if(dateObj.part && !dateObj.month) { + cslItem[variable].season = dateObj.part; + } + } else { + // if no year, pass date literally + cslItem[variable] = {"literal":date}; + } + } + } + + //this._cache[zoteroItem.id] = cslItem; + return cslItem; + }, -Zotero.Cite.labels = ["page", "book", "chapter", "column", "figure", "folio", - "issue", "line", "note", "opus", "paragraph", "part", "section", "sub verbo", - "volume", "verse"]; -\ No newline at end of file + /** + * citeproc-js system function for getting locale + * See http://gsl-nagoya-u.net/http/pub/citeproc-doc.html#retrieveLocale + * @param {String} lang Language to look for a locale for + * @return {String|Boolean} The locale as a string if it exists, or false if it doesn't + */ + "retrieveLocale":function retrieveLocale(lang) { + var protHandler = Components.classes["@mozilla.org/network/protocol;1?name=chrome"] + .createInstance(Components.interfaces.nsIProtocolHandler); + try { + var channel = protHandler.newChannel(protHandler.newURI("chrome://zotero/content/locale/csl/locales-"+lang+".xml", "UTF-8", null)); + var rawStream = channel.open(); + } catch(e) { + return false; + } + var converterStream = Components.classes["@mozilla.org/intl/converter-input-stream;1"] + .createInstance(Components.interfaces.nsIConverterInputStream); + converterStream.init(rawStream, "UTF-8", 65535, + Components.interfaces.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER); + var str = {}; + converterStream.readString(channel.contentLength, str); + converterStream.close(); + return str.value; + }, + + /** + * citeproc-js system function for getting abbreviations + * See http://gsl-nagoya-u.net/http/pub/citeproc-doc.html#getabbreviations + * Not currently used because it doesn't scale well to large lists + */ + "getAbbreviations":function getAbbreviations() { + return {}; + } +}; +\ No newline at end of file