www

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

cite.js (21254B)


      1 "use strict";
      2 
      3 /**
      4  * Utility functions for dealing with citations
      5  * @namespace
      6  */
      7 Zotero.Cite = {
      8 	/**
      9 	 * Locator labels
     10 	 */
     11 	"labels":["page", "book", "chapter", "column", "figure", "folio",
     12 		"issue", "line", "note", "opus", "paragraph", "part", "section", "sub verbo",
     13 		"volume", "verse"],
     14 	
     15 	/**
     16 	 * Remove specified item IDs in-place from a citeproc-js bibliography object returned
     17 	 * by makeBibliography()
     18 	 * @param {bib} citeproc-js bibliography object
     19 	 * @param {Set} itemsToRemove Set of items to remove
     20 	 */
     21 	"removeFromBibliography":function(bib, itemsToRemove) {
     22 		var removeItems = [];
     23 		for(let i in bib[0].entry_ids) {
     24 			for(let j in bib[0].entry_ids[i]) {
     25 				if(itemsToRemove.has(`${bib[0].entry_ids[i][j]}`)) {
     26 					removeItems.push(i);
     27 					break;
     28 				}
     29 			}
     30 		}
     31 		for(let i=removeItems.length-1; i>=0; i--) {
     32 			bib[0].entry_ids.splice(removeItems[i], 1);
     33 			bib[1].splice(removeItems[i], 1);
     34 		}
     35 	},
     36 
     37 	/**
     38 	 * Convert formatting data from citeproc-js bibliography object into explicit format
     39 	 * parameters for RTF or word processors
     40 	 * @param {bib} citeproc-js bibliography object
     41 	 * @return {Object} Bibliography style parameters.
     42 	 */
     43 	"getBibliographyFormatParameters":function getBibliographyFormatParameters(bib) {
     44 		var bibStyle = {"tabStops":[], "indent":0, "firstLineIndent":0,
     45 						"lineSpacing":(240*bib[0].linespacing),
     46 						"entrySpacing":(240*bib[0].entryspacing)};
     47 		if(bib[0].hangingindent) {
     48 			bibStyle.indent = 720;				// 720 twips = 0.5 in
     49 			bibStyle.firstLineIndent = -720;	// -720 twips = -0.5 in
     50 		} else if(bib[0]["second-field-align"]) {
     51 			// this is a really sticky issue. the below works for first fields that look like "[1]"
     52 			// and "1." otherwise, i have no idea. luckily, this will be good enough 99% of the time.
     53 			var alignAt = 24+bib[0].maxoffset*120;
     54 			bibStyle.firstLineIndent = -alignAt;
     55 			if(bib[0]["second-field-align"] == "margin") {
     56 				bibStyle.tabStops = [0];
     57 			} else {
     58 				bibStyle.indent = alignAt;
     59 				bibStyle.tabStops = [alignAt];
     60 			}
     61 		}
     62 		
     63 		return bibStyle;
     64 	},
     65 
     66 	/**
     67 	 * Makes a formatted bibliography, if the style defines one; otherwise makes a 
     68 	 * formatted list of items
     69 	 * @param {Zotero.Style} style The style to use
     70 	 * @param {Zotero.Item[]} items An array of items
     71 	 * @param {String} format The format of the output (html, text, or rtf)
     72 	 * @return {String} Bibliography or item list in specified format
     73 	 */
     74 	"makeFormattedBibliographyOrCitationList":function(cslEngine, items, format, asCitationList) {
     75 		cslEngine.setOutputFormat(format);
     76 		cslEngine.updateItems(items.map(item => item.id));
     77 		 		
     78 		if(!asCitationList) {
     79 			var bibliography = Zotero.Cite.makeFormattedBibliography(cslEngine, format);
     80 			if(bibliography) return bibliography;
     81 		}
     82 		
     83 		var styleClass = cslEngine.opt.class;
     84 		var citations=[];
     85 		for (var i=0, ilen=items.length; i<ilen; i++) {
     86 			var item = items[i];
     87 			var outList = cslEngine.appendCitationCluster({"citationItems":[{"id":item.id}], "properties":{}}, true);
     88 			for (var j=0, jlen=outList.length; j<jlen; j++) {
     89 				var citationPos = outList[j][0];
     90 				citations[citationPos] = outList[j][1];
     91 			}
     92 		}
     93 		
     94 		if(styleClass == "note") {
     95 			if(format == "html") {
     96 				return "<ol>\n\t<li>"+citations.join("</li>\n\t<li>")+"</li>\n</ol>";
     97 			} else if(format == "text") {
     98 				var output = [];
     99 				for(var i=0; i<citations.length; i++) {
    100 					output.push((i+1)+". "+citations[i]+"\r\n");
    101 				}
    102 				return output.join("");
    103 			} else if(format == "rtf") {
    104 				var output = ["{\\rtf \n{\\*\\listtable{\\list\\listtemplateid1\\listhybrid{\\listlevel"+
    105 					"\\levelnfc0\\levelnfcn0\\leveljc0\\leveljcn0\\levelfollow0\\levelstartat1"+
    106 					"\\levelspace360\\levelindent0{\\*\\levelmarker \\{decimal\\}.}{\\leveltext"+
    107 					"\\leveltemplateid1\\'02\\'00.;}{\\levelnumbers\\'01;}\\fi-360\\li720\\lin720 }"+
    108 					"{\\listname ;}\\listid1}}\n{\\*\\listoverridetable{\\listoverride\\listid1"+
    109 					"\\listoverridecount0\\ls1}}\n\\tx720\\li720\\fi-480\\ls1\\ilvl0\n"];
    110 				for(var i=0; i<citations.length; i++) {
    111 					output.push("{\\listtext "+(i+1)+".	}"+citations[i]+"\\\n");
    112 				}
    113 				output.push("}");
    114 				return output.join("");
    115 			} else {
    116 				throw "Unimplemented bibliography format "+format;
    117 			}
    118 		} else {
    119 			if(format == "html") {
    120 				return citations.join("<br />");
    121 			} else if(format == "text") {
    122 				return citations.join("\r\n");
    123 			} else if(format == "rtf") {
    124 				return "<\\rtf \n"+citations.join("\\\n")+"\n}";
    125 			}
    126 		}
    127 	},
    128 	
    129 	/**
    130 	 * Makes a formatted bibliography
    131 	 * @param {Zotero.Style} style The style
    132 	 * @param {String} format The format of the output (html, text, or rtf)
    133 	 * @return {String} Bibliography in specified format
    134 	 */
    135 	"makeFormattedBibliography":function makeFormattedBibliography(cslEngine, format) {
    136 		cslEngine.setOutputFormat(format);
    137 		var bib = cslEngine.makeBibliography();
    138 		if(!bib) return false;
    139 		
    140 		if(format == "html") {
    141 			var output = [bib[0].bibstart];
    142 			for(var i in bib[1]) {
    143 				output.push(bib[1][i]);
    144 				
    145 				// add COinS
    146 				for (let itemID of bib[0].entry_ids[i]) {
    147 					try {
    148 						var co = Zotero.OpenURL.createContextObject(Zotero.Items.get(itemID), "1.0");
    149 						if(!co) continue;
    150 						output.push('  <span class="Z3988" title="'+
    151 							co.replace("&", "&amp;", "g").replace("<", "&lt;", "g").replace(">", "&gt;", "g")+
    152 							'"></span>\n');
    153 					} catch(e) {
    154 						Zotero.logError(e);
    155 					}
    156 				}
    157 			}
    158 			output.push(bib[0].bibend);
    159 			var html = output.join("");
    160 			
    161 			var inlineCSS = true;
    162 			if (!inlineCSS) {
    163 				return html;
    164 			}
    165 			
    166 			//Zotero.debug("maxoffset: " + bib[0].maxoffset);
    167 			//Zotero.debug("entryspacing: " + bib[0].entryspacing);
    168 			//Zotero.debug("linespacing: " + bib[0].linespacing);
    169 			//Zotero.debug("hangingindent: " + bib[0].hangingindent);
    170 			//Zotero.debug("second-field-align: " + bib[0]["second-field-align"]);
    171 			
    172 			var maxOffset = parseInt(bib[0].maxoffset);
    173 			var entrySpacing = parseInt(bib[0].entryspacing);
    174 			var lineSpacing = parseInt(bib[0].linespacing);
    175 			var hangingIndent = parseInt(bib[0].hangingindent);
    176 			var secondFieldAlign = bib[0]["second-field-align"];
    177 			
    178 			// Validate input
    179 			if(maxOffset == NaN) throw "Invalid maxoffset";
    180 			if(entrySpacing == NaN) throw "Invalid entryspacing";
    181 			if(lineSpacing == NaN) throw "Invalid linespacing";
    182 			
    183 			var str;
    184 			var parser = Components.classes["@mozilla.org/xmlextras/domparser;1"]
    185 					.createInstance(Components.interfaces.nsIDOMParser),
    186 				doc = parser.parseFromString("<!DOCTYPE html><html><body></body></html>", "text/html");
    187 			doc.body.insertAdjacentHTML("afterbegin", html);
    188 			var div = doc.body.firstChild,
    189 				leftMarginDivs = Zotero.Utilities.xpath(doc, '//div[@class="csl-left-margin"]'),
    190 				multiField = !!leftMarginDivs.length,
    191 				clearEntries = multiField;
    192 			
    193 			// One of the characters is usually a period, so we can adjust this down a bit
    194 			maxOffset = Math.max(1, maxOffset - 2);
    195 			
    196 			// Force a minimum line height
    197 			if(lineSpacing <= 1.35) lineSpacing = 1.35;
    198 			
    199 			var style = div.getAttribute("style");
    200 			if(!style) style = "";
    201 			style += "line-height: " + lineSpacing + "; ";
    202 			
    203 			if(hangingIndent) {
    204 				if (multiField && !secondFieldAlign) {
    205 					throw ("second-field-align=false and hangingindent=true combination is not currently supported");
    206 				}
    207 				// If only one field, apply hanging indent on root
    208 				else if (!multiField) {
    209 					style += "margin-left: " + hangingIndent + "em; text-indent:-" + hangingIndent + "em;";
    210 				}
    211 			}
    212 			
    213 			if(style) div.setAttribute("style", style);
    214 			
    215 			// csl-entry
    216 			var divs = Zotero.Utilities.xpath(doc, '//div[@class="csl-entry"]');
    217 			for(var i=0, n=divs.length; i<n; i++) {
    218 				var div = divs[i],
    219 					divStyle = div.getAttribute("style");
    220 				if(!divStyle) divStyle = "";
    221 				
    222 				if (clearEntries) {
    223 					divStyle += "clear: left; ";
    224 				}
    225 				
    226 				if(entrySpacing && i !== n - 1) {
    227 					divStyle += "margin-bottom: " + entrySpacing + "em;";
    228 				}
    229 				
    230 				if(divStyle) div.setAttribute("style", divStyle);
    231 			}
    232 			
    233 			// Padding on the label column, which we need to include when
    234 			// calculating offset of right column
    235 			var rightPadding = .5;
    236 			
    237 			// div.csl-left-margin
    238 			for (let div of leftMarginDivs) {
    239 				var divStyle = div.getAttribute("style");
    240 				if(!divStyle) divStyle = "";
    241 				
    242 				divStyle = "float: left; padding-right: " + rightPadding + "em;";
    243 				
    244 				// Right-align the labels if aligning second line, since it looks
    245 				// better and we don't need the second line of text to align with
    246 				// the left edge of the label
    247 				if (secondFieldAlign) {
    248 					divStyle += "text-align: right; width: " + maxOffset + "em;";
    249 				}
    250 				
    251 				div.setAttribute("style", divStyle);
    252 			}
    253 			
    254 			// div.csl-right-inline
    255 			for (let div of Zotero.Utilities.xpath(doc, '//div[@class="csl-right-inline"]')) {
    256 				var divStyle = div.getAttribute("style");
    257 				if(!divStyle) divStyle = "";
    258 				
    259 				divStyle = "margin: 0 .4em 0 " + (secondFieldAlign ? maxOffset + rightPadding : "0") + "em;";
    260 				
    261 				if (hangingIndent) {
    262 					divStyle += "padding-left: " + hangingIndent + "em; text-indent:-" + hangingIndent + "em;";
    263 				}
    264 				
    265 				div.setAttribute("style", divStyle);
    266 			}
    267 			
    268 			// div.csl-indent
    269 			for (let div of Zotero.Utilities.xpath(doc, '//div[@class="csl-indent"]')) {
    270 				div.setAttribute("style", "margin: .5em 0 0 2em; padding: 0 0 .2em .5em; border-left: 5px solid #ccc;");
    271 			}
    272 			
    273 			return doc.body.innerHTML;
    274 		} else if(format == "text") {
    275 			return bib[0].bibstart+bib[1].join("")+bib[0].bibend;
    276 		} else if(format == "rtf") {
    277 			var bibStyle = Zotero.Cite.getBibliographyFormatParameters(bib);
    278 			
    279 			var preamble = (bibStyle.tabStops.length ? "\\tx"+bibStyle.tabStops.join(" \\tx")+" " : "");
    280 			preamble += "\\li"+bibStyle.indent+" \\fi"+bibStyle.firstLineIndent+" "
    281 					   +"\\sl"+bibStyle.lineSpacing+" \\slmult1 "
    282 					   +"\\sa"+bibStyle.entrySpacing+" ";
    283 			
    284 			return bib[0].bibstart+preamble+bib[1].join("\\\r\n")+"\\\r\n"+bib[0].bibend;
    285 		} else {
    286 			throw "Unimplemented bibliography format "+format;
    287 		}
    288 	},
    289 
    290 	/**
    291 	 * Get an item by ID, either by retrieving it from the library or looking for the document it
    292 	 * belongs to.
    293 	 * @param {String|Number|Array} id
    294 	 * @return {Zotero.Item} item
    295 	 */
    296 	"getItem":function getItem(id) {
    297 		var slashIndex;
    298 		
    299 		if(id instanceof Array) {
    300 			return id.map(anId => Zotero.Cite.getItem(anId));
    301 		} else if(typeof id === "string" && (slashIndex = id.indexOf("/")) !== -1) {		
    302 			var sessionID = id.substr(0, slashIndex),
    303 				session = Zotero.Integration.sessions[sessionID],
    304 				item;
    305 			if (session) {
    306 				item = session.embeddedZoteroItems[id.substr(slashIndex+1)];
    307 			}
    308 			
    309 			if(!item) {
    310 				item = new Zotero.Item("document");
    311 				item.setField("title", "Missing Item");
    312 				Zotero.log("CSL item "+id+" not found");
    313 			}
    314 			return item;
    315 		} else {
    316 			return Zotero.Items.get(id);
    317 		}
    318 	},
    319 	
    320 	extraToCSL: function (extra) {
    321 		return extra.replace(/^([A-Za-z \-]+)(:\s*.+)/gm, function (_, field, value) {
    322 			var originalField = field;
    323 			var field = field.toLowerCase().replace(/ /g, '-');
    324 			// Fields from https://aurimasv.github.io/z2csl/typeMap.xml
    325 			switch (field) {
    326 			// Standard fields
    327 			case 'abstract':
    328 			case 'accessed':
    329 			case 'annote':
    330 			case 'archive':
    331 			case 'archive-place':
    332 			case 'author':
    333 			case 'authority':
    334 			case 'call-number':
    335 			case 'chapter-number':
    336 			case 'citation-label':
    337 			case 'citation-number':
    338 			case 'collection-editor':
    339 			case 'collection-number':
    340 			case 'collection-title':
    341 			case 'composer':
    342 			case 'container':
    343 			case 'container-author':
    344 			case 'container-title':
    345 			case 'container-title-short':
    346 			case 'dimensions':
    347 			case 'director':
    348 			case 'edition':
    349 			case 'editor':
    350 			case 'editorial-director':
    351 			case 'event':
    352 			case 'event-date':
    353 			case 'event-place':
    354 			case 'first-reference-note-number':
    355 			case 'genre':
    356 			case 'illustrator':
    357 			case 'interviewer':
    358 			case 'issue':
    359 			case 'issued':
    360 			case 'jurisdiction':
    361 			case 'keyword':
    362 			case 'language':
    363 			case 'locator':
    364 			case 'medium':
    365 			case 'note':
    366 			case 'number':
    367 			case 'number-of-pages':
    368 			case 'number-of-volumes':
    369 			case 'original-author':
    370 			case 'original-date':
    371 			case 'original-publisher':
    372 			case 'original-publisher-place':
    373 			case 'original-title':
    374 			case 'page':
    375 			case 'page-first':
    376 			case 'publisher':
    377 			case 'publisher-place':
    378 			case 'recipient':
    379 			case 'references':
    380 			case 'reviewed-author':
    381 			case 'reviewed-title':
    382 			case 'scale':
    383 			case 'section':
    384 			case 'source':
    385 			case 'status':
    386 			case 'submitted':
    387 			case 'title':
    388 			case 'title-short':
    389 			case 'translator':
    390 			case 'version':
    391 			case 'volume':
    392 			case 'year-suffix':
    393 				break;
    394 			
    395 			// Uppercase fields
    396 			case 'doi':
    397 			case 'isbn':
    398 			case 'issn':
    399 			case 'pmcid':
    400 			case 'pmid':
    401 			case 'url':
    402 				field = field.toUpperCase();
    403 				break;
    404 			
    405 			// Weirdo
    406 			case 'archive-location':
    407 				field = 'archive_location';
    408 				break;
    409 			
    410 			// Don't change other lines
    411 			default:
    412 				field = originalField;
    413 			}
    414 			return field + value;
    415 		});
    416 	}
    417 };
    418 
    419 /**
    420  * Get a CSL abbreviation in the format expected by citeproc-js
    421  */
    422 Zotero.Cite.getAbbreviation = new function() {
    423 	var abbreviations,
    424 		abbreviationCategories;
    425 
    426 	/**
    427 	 * Initialize abbreviations database.
    428 	 */
    429 	function init() {
    430 		if(!abbreviations) loadAbbreviations();
    431 	}
    432 
    433 	function loadAbbreviations() {
    434 		var file = Zotero.File.pathToFile(Zotero.DataDirectory.dir);
    435 		file.append("abbreviations.json");
    436 
    437 		var json, origin;
    438 		if(file.exists()) {
    439 			json = Zotero.File.getContents(file);
    440 			origin = file.path;
    441 		} else {
    442 			json = Zotero.File.getContentsFromURL("resource://zotero/schema/abbreviations.json");
    443 			origin = "resource://zotero/schema/abbreviations.json";
    444 		}
    445 
    446 		try {
    447 			abbreviations = JSON.parse(json);
    448 		} catch(e) {
    449 			throw new Zotero.Exception.Alert("styles.abbreviations.parseError", origin,
    450 				"styles.abbreviations.title", e);
    451 		}
    452 
    453 		if(!abbreviations.info || !abbreviations.info.name || !abbreviations.info.URI) {
    454 			throw new Zotero.Exception.Alert("styles.abbreviations.missingInfo", origin,
    455 				"styles.abbreviations.title");
    456 		}
    457 
    458 		abbreviationCategories = {};
    459 		for(var jurisdiction in abbreviations) {
    460 			for(var category in abbreviations[jurisdiction]) {
    461 				abbreviationCategories[category] = true;
    462 			}
    463 		}
    464 	}
    465 	
    466 	/**
    467 	 * Normalizes a key
    468 	 */
    469 	function normalizeKey(key) {
    470 		// Strip periods, normalize spacing, and convert to lowercase
    471 		return key.toString().
    472 			replace(/(?:\b|^)(?:and|et|y|und|l[ae]|the|[ld]')(?:\b|$)|[\x21-\x2C.\/\x3A-\x40\x5B-\x60\\\x7B-\x7E]/ig, "").
    473 			replace(/\s+/g, " ").trim();
    474 	}
    475 
    476 	function lookupKey(key) {
    477 		return key.toLowerCase().replace(/\s*\./g, "." );
    478 	}
    479 	
    480 	/**
    481 	 * Replace getAbbreviation on citeproc-js with our own handler.
    482 	 */
    483 	return function getAbbreviation(listname, obj, jurisdiction, category, key) {
    484 		init();
    485 
    486 		// Short circuit if we know we don't handle this kind of abbreviation
    487 		if(!abbreviationCategories[category] && !abbreviationCategories[category+"-word"]) return;
    488 
    489 		var normalizedKey = normalizeKey(key),
    490 			lcNormalizedKey = lookupKey(normalizedKey),
    491 			abbreviation;
    492 		if(!normalizedKey) return;
    493 		
    494 		var jurisdictions = ["default"];
    495 		if(jurisdiction !== "default" && abbreviations[jurisdiction]) {
    496 			jurisdictions.unshift(jurisdiction);
    497 		}
    498 
    499 		// Look for full abbreviation
    500 		var jur, cat;
    501 		for(var i=0; i<jurisdictions.length && !abbreviation; i++) {
    502 			if((jur = abbreviations[jurisdictions[i]]) && (cat = jur[category])) {
    503 				abbreviation = cat[lcNormalizedKey];
    504 			}
    505 		}
    506 
    507 		if(!abbreviation) {
    508 			// Abbreviate words individually
    509 			var words = normalizedKey.split(/([ \-])/);
    510 
    511 			if(words.length > 1) {
    512 				var lcWords = [];
    513 				for(var j=0; j<words.length; j+=2) {
    514 					lcWords[j] = lookupKey(words[j]);
    515 				}
    516 				for(var j=0; j<words.length; j+=2) {
    517 					var word = words[j],
    518 						lcWord = lcWords[j],
    519 						newWord = undefined,
    520 						exactMatch = false;
    521 					
    522 					for(var i=0; i<jurisdictions.length && newWord === undefined; i++) {
    523 						if(!(jur = abbreviations[jurisdictions[i]])) continue;
    524 						if(!(cat = jur[category+"-word"])) continue;
    525 						
    526 						if(cat.hasOwnProperty(lcWord)) {
    527 							// Complete match
    528 							newWord = cat[lcWord];
    529 							exactMatch = true;
    530 						} else if(lcWord.charAt(lcWord.length-1) == 's' && cat.hasOwnProperty(lcWord.substr(0, lcWord.length-1))) {
    531 							// Try dropping 's'
    532 							newWord = cat[lcWord.substr(0, lcWord.length-1)];
    533 							exactMatch = true;
    534 						} else {
    535 							if(j < words.length-2) {
    536 								// Two-word match
    537 								newWord = cat[lcWord+words[j+1]+lcWords[j+2]];
    538 								if(newWord !== undefined) {
    539 									words.splice(j+1, 2);
    540 									lcWords.splice(j+1, 2);
    541 									exactMatch = true;
    542 								}
    543 							}
    544 
    545 							if(newWord === undefined) {
    546 								// Partial match
    547 								for(var k=lcWord.length; k>0 && newWord === undefined; k--) {
    548 									newWord = cat[lcWord.substr(0, k)+"-"];
    549 								}
    550 							}
    551 						}
    552 					}
    553 					
    554 					// Don't substitute with a longer word
    555 					if(newWord && !exactMatch && word.length - newWord.length < 1) {
    556 						newWord = word;
    557 					}
    558 					
    559 					// Fall back to full word
    560 					if(newWord === undefined) newWord = word;
    561 					
    562 					// Don't discard last word (e.g. Climate of the Past => Clim. Past)
    563 					if(!newWord && j == words.length-1) newWord = word;
    564 					
    565 					words[j] = newWord.substr(0, 1).toUpperCase() + newWord.substr(1);
    566 				}
    567 				abbreviation = words.join("").replace(/\s+/g, " ").trim();
    568 			} else {
    569 				abbreviation = key;
    570 			}
    571 		}
    572 
    573 		if(!abbreviation) abbreviation = key; //this should never happen, but just in case
    574 		
    575 		Zotero.debug("Abbreviated "+key+" as "+abbreviation);
    576 		
    577 		// Add to jurisdiction object
    578 		if(!obj[jurisdiction]) {
    579 			obj[jurisdiction] = new Zotero.CiteProc.CSL.AbbreviationSegments();
    580 		}
    581 		obj[jurisdiction][category][key] = abbreviation;
    582 	}
    583 };
    584 
    585 /**
    586  * citeproc-js system object
    587  * @class
    588  */
    589 Zotero.Cite.System = function(automaticJournalAbbreviations) {
    590 	if(automaticJournalAbbreviations) {
    591 		this.getAbbreviation = Zotero.Cite.getAbbreviation;
    592 	}
    593 }
    594 
    595 Zotero.Cite.System.prototype = {
    596 	/**
    597 	 * citeproc-js system function for getting items
    598 	 * See http://gsl-nagoya-u.net/http/pub/citeproc-doc.html#retrieveitem
    599 	 * @param {String|Integer} item - Item ID, or string item for embedded citations
    600 	 * @return {Object} citeproc-js item
    601 	 */
    602 	"retrieveItem":function retrieveItem(item) {
    603 		var zoteroItem, slashIndex;
    604 		if(typeof item === "object" && item !== null &&  item instanceof Zotero.Item) {
    605 			//if(this._cache[item.id]) return this._cache[item.id];
    606 			zoteroItem = item;
    607 		} else if(typeof item === "string" && (slashIndex = item.indexOf("/")) !== -1) {
    608 			// is an embedded item
    609 			var sessionID = item.substr(0, slashIndex);
    610 			var session = Zotero.Integration.sessions[sessionID];
    611 			if(session) {
    612 				var embeddedCitation = session.embeddedItems[item.substr(slashIndex+1)];
    613 				if (embeddedCitation) {
    614 					embeddedCitation.id = item;
    615 					return embeddedCitation;
    616 				}
    617 			}
    618 		} else {
    619 			// is an item ID
    620 			//if(this._cache[item]) return this._cache[item];
    621 			try {
    622 				zoteroItem = Zotero.Items.get(item);
    623 			} catch(e) {}
    624 		}
    625 
    626 		if(!zoteroItem) {
    627 			throw new Error("Zotero.Cite.System.retrieveItem called on non-item "+item);
    628 		}
    629 		
    630 		var cslItem = Zotero.Utilities.itemToCSLJSON(zoteroItem);
    631 		
    632 		// TEMP: citeproc-js currently expects the id property to be the item DB id
    633 		cslItem.id = zoteroItem.id;
    634 		
    635 		if (!Zotero.Prefs.get("export.citePaperJournalArticleURL")) {
    636 			var itemType = Zotero.ItemTypes.getName(zoteroItem.itemTypeID);
    637 			// don't return URL or accessed information for journal articles if a
    638 			// pages field exists
    639 			if (["journalArticle", "newspaperArticle", "magazineArticle"].indexOf(itemType) !== -1
    640 				&& zoteroItem.getField("pages")
    641 			) {
    642 				delete cslItem.URL;
    643 				delete cslItem.accessed;
    644 			}
    645 		}
    646 		
    647 		return cslItem;
    648 	},
    649 
    650 	/**
    651 	 * citeproc-js system function for getting locale
    652 	 * See http://gsl-nagoya-u.net/http/pub/citeproc-doc.html#retrieveLocale
    653 	 * @param {String} lang Language to look for a locale for
    654 	 * @return {String|Boolean} The locale as a string if it exists, or false if it doesn't
    655 	 */
    656 	"retrieveLocale":function retrieveLocale(lang) {
    657 		return Zotero.Cite.Locale.get(lang);
    658 	}
    659 };
    660 
    661 Zotero.Cite.Locale = {
    662 	_cache: new Map(),
    663 	
    664 	get: function (locale) {
    665 		var str = this._cache.get(locale);
    666 		if (str) {
    667 			return str;
    668 		}
    669 		var uri = `chrome://zotero/content/locale/csl/locales-${locale}.xml`;
    670 		try {
    671 			let protHandler = Components.classes["@mozilla.org/network/protocol;1?name=chrome"]
    672 				.createInstance(Components.interfaces.nsIProtocolHandler);
    673 			let channel = protHandler.newChannel(protHandler.newURI(uri));
    674 			let cstream = Components.classes["@mozilla.org/intl/converter-input-stream;1"]
    675 				.createInstance(Components.interfaces.nsIConverterInputStream);
    676 			cstream.init(channel.open(), "UTF-8", 0, 0);
    677 			let obj = {};
    678 			let read = 0;
    679 			let str = "";
    680 			do {
    681 				// Read as much as we can and put it in obj.value
    682 				read = cstream.readString(0xffffffff, obj);
    683 				str += obj.value;
    684 			} while (read != 0);
    685 			cstream.close();
    686 			this._cache.set(locale, str);
    687 			return str;
    688 		}
    689 		catch (e) {
    690 			//Zotero.debug(e);
    691 			return false;
    692 		}
    693 	}
    694 };