diff --git a/chrome/content/zotero/xpcom/cite.js b/chrome/content/zotero/xpcom/cite.js
index dac7c6386..912c94f42 100644
--- 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";
+
+ 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 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 "
\n\t- "+citations.join("
\n\t- ")+"
\n
";
+ } else if(format == "text") {
+ var output = [];
+ for(var i=0; i");
+ } else if(format == "text") {
+ return citations.join("\r\n");
+ } else if(format == "rtf") {
+ return "<\\rtf \n"+citations.join("\\\n")+"\n}";
+ }
}
-
- 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);
+ /**
+ * 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(' ", ">", "g")+
+ '"/>\n');
+ } catch(e) {
+ Zotero.logError(e);
}
}
- cslItem[variable] = {"date-parts":[dateParts]};
+ }
+ 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");
- // if no month, use season as month
- if(dateObj.part && !dateObj.month) {
- cslItem[variable].season = dateObj.part;
+ var leftMarginDivs = Zotero.Utilities.xpath(doc, '//div[@class="csl-left-margin"]'),
+ multiField = !!leftMarginDivs.length,
+ clearEntries = multiField;
+
+ // 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 style = doc.documentElement.getAttribute("style");
+ if(!style) style = "";
+ style += "line-height: " + lineSpacing + "; ";
+
+ 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;";
+ }
}
- } else {
- // if no year, pass date literally
- cslItem[variable] = {"literal":date};
+
+ 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=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 "\n\t- "+citations.join("
\n\t- ")+"
\n
";
+
+ return str;
} else if(format == "text") {
- var output = [];
- for(var i=0; i");
- } else if(format == "text") {
- return citations.join("\r\n");
- } else if(format == "rtf") {
- return "<\\rtf \n"+citations.join("\\\n")+"\n}";
+ },
+
+ /**
+ * 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;
+
+ 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);
}
}
-}
+};
/**
- * Makes a formatted bibliography
- * @param {Zotero.Style} style The style
- * @param {Zotero.Item[]} items An array of items
+ * citeproc-js system object
+ * @namespace
*/
-Zotero.Cite.makeFormattedBibliography = function(cslEngine, format) {
- cslEngine.setOutputFormat(format);
- var bib = cslEngine.makeBibliography();
- if(!bib) return false;
+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(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(' ", ">", "g")+
- '"/>\n');
- } catch(e) {
- Zotero.logError(e);
+ 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(/^".+"$/)) {
+ value = value.substr(1, value.length-2);
+ }
+ cslItem[variable] = value;
+ break;
}
}
}
- output.push(bib[0].bibend);
- var html = output.join("");
- var inlineCSS = true;
- if (!inlineCSS) {
- return html;
+ // 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];
+ }
}
- //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"]);
+ // 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};
+ }
+ }
+ }
- 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;
+ //this._cache[zoteroItem.id] = cslItem;
+ return cslItem;
+ },
+
+ /**
+ * 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 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;
-
- // 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 style = doc.documentElement.getAttribute("style");
- if(!style) style = "";
- style += "line-height: " + lineSpacing + "; ";
-
- 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;";
- }
- }
-
- 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