citeproc-rs support (#2220)
Disabled under zotero.cite.useCiteprocRs by default
This commit is contained in:
parent
55e8f7914b
commit
44b6cd0525
22 changed files with 7429 additions and 182 deletions
|
@ -750,7 +750,7 @@ var Zotero_File_Interface = new function() {
|
|||
var clipboardService = Components.classes["@mozilla.org/widget/clipboard;1"].
|
||||
getService(Components.interfaces.nsIClipboard);
|
||||
style = Zotero.Styles.get(style);
|
||||
var cslEngine = style.getCiteProc(locale);
|
||||
var cslEngine = style.getCiteProc(locale, 'html');
|
||||
|
||||
if (asCitations) {
|
||||
cslEngine.updateItems(items.map(item => item.id));
|
||||
|
@ -779,10 +779,12 @@ var Zotero_File_Interface = new function() {
|
|||
else {
|
||||
// Generate engine again to work around citeproc-js problem:
|
||||
// https://github.com/zotero/zotero/commit/4a475ff3
|
||||
cslEngine = style.getCiteProc(locale);
|
||||
output = Zotero.Cite.makeFormattedBibliographyOrCitationList(cslEngine, items, "text");
|
||||
cslEngine.free();
|
||||
cslEngine = style.getCiteProc(locale, 'text');
|
||||
output = Zotero.Cite.makeFormattedBibliographyOrCitationList(cslEngine, items, 'text');
|
||||
}
|
||||
}
|
||||
cslEngine.free();
|
||||
|
||||
var str = Components.classes["@mozilla.org/supports-string;1"].
|
||||
createInstance(Components.interfaces.nsISupportsString);
|
||||
|
@ -831,7 +833,7 @@ var Zotero_File_Interface = new function() {
|
|||
}
|
||||
else {
|
||||
var style = Zotero.Styles.get(io.style);
|
||||
var cslEngine = style.getCiteProc(locale);
|
||||
var cslEngine = style.getCiteProc(locale, format);
|
||||
var bibliography = Zotero.Cite.makeFormattedBibliographyOrCitationList(cslEngine,
|
||||
items, format, io.mode === "citations");
|
||||
}
|
||||
|
|
|
@ -465,8 +465,7 @@ var Zotero_RTFScan = new function() {
|
|||
// load style and create ItemSet with all items
|
||||
var zStyle = Zotero.Styles.get(document.getElementById("style-listbox").value)
|
||||
var locale = document.getElementById("locale-menu").value;
|
||||
var style = zStyle.getCiteProc(locale);
|
||||
style.setOutputFormat("rtf");
|
||||
var style = zStyle.getCiteProc(locale, 'rtf');
|
||||
var isNote = zStyle.class == "note";
|
||||
|
||||
// create citations
|
||||
|
@ -564,6 +563,8 @@ var Zotero_RTFScan = new function() {
|
|||
}
|
||||
}
|
||||
|
||||
cslEngine.free();
|
||||
|
||||
Zotero.File.putContents(outputFile, contents);
|
||||
|
||||
// save locale
|
||||
|
|
|
@ -173,7 +173,7 @@ var Zotero_CSL_Editor = new function() {
|
|||
var selectedLocale = document.getElementById("locale-menu").value;
|
||||
var styleEngine;
|
||||
try {
|
||||
styleEngine = style.getCiteProc(style.locale || selectedLocale);
|
||||
styleEngine = style.getCiteProc(style.locale || selectedLocale, 'html');
|
||||
} catch(e) {
|
||||
iframe.contentDocument.documentElement.innerHTML = '<div>' + Zotero.getString('styles.editor.warning.parseError') + '</div><div>'+e+'</div>';
|
||||
throw e;
|
||||
|
@ -231,7 +231,7 @@ var Zotero_CSL_Editor = new function() {
|
|||
iframe.contentDocument.documentElement.innerHTML = '<div>' + Zotero.getString('styles.editor.warning.renderError') + '</div><div>'+e+'</div>';
|
||||
throw e;
|
||||
}
|
||||
editor.styleEngine = styleEngine;
|
||||
styleEngine.free();
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -35,6 +35,7 @@
|
|||
id="csl-edit"
|
||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
onload="Zotero_CSL_Editor.init();"
|
||||
onunload="Zotero_CSL_Editor.onUnload()"
|
||||
title="&styles.editor;">
|
||||
|
||||
<script src="chrome://zotero/content/include.js"/>
|
||||
|
|
|
@ -99,7 +99,7 @@ var Zotero_CSL_Preview = new function() {
|
|||
}
|
||||
|
||||
var locale = document.getElementById("locale-menu").value;
|
||||
var styleEngine = style.getCiteProc(locale);
|
||||
var styleEngine = style.getCiteProc(locale, 'html');
|
||||
|
||||
// Generate multiple citations
|
||||
var citations = styleEngine.previewCitationCluster(
|
||||
|
@ -117,6 +117,8 @@ var Zotero_CSL_Preview = new function() {
|
|||
bibliography = Zotero.Cite.makeFormattedBibliography(styleEngine, "html");
|
||||
}
|
||||
|
||||
styleEngine.free();
|
||||
|
||||
return '<p>' + citations + '</p>' + bibliography;
|
||||
}
|
||||
|
||||
|
|
|
@ -133,7 +133,6 @@ Zotero.Cite = {
|
|||
* @return {String} Bibliography in specified format
|
||||
*/
|
||||
"makeFormattedBibliography":function makeFormattedBibliography(cslEngine, format) {
|
||||
cslEngine.setOutputFormat(format);
|
||||
var bib = cslEngine.makeBibliography();
|
||||
if(!bib) return false;
|
||||
|
||||
|
|
285
chrome/content/zotero/xpcom/citeprocRsBridge.js
Normal file
285
chrome/content/zotero/xpcom/citeprocRsBridge.js
Normal file
|
@ -0,0 +1,285 @@
|
|||
/*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright © 2019 Center for History and New Media
|
||||
George Mason University, Fairfax, Virginia, USA
|
||||
http://zotero.org
|
||||
|
||||
This file is part of Zotero.
|
||||
|
||||
Zotero is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Zotero is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with Zotero. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***** END LICENSE BLOCK *****
|
||||
*/
|
||||
|
||||
(function() {
|
||||
|
||||
const additionalCSLProperties = ['locator', 'label', 'suppress-author', 'author-only', 'prefix', 'suffix'];
|
||||
|
||||
/**
|
||||
* Bridging code which allows to use citeproc-js and citeproc-rs almost interchangeably
|
||||
* without big alterations to existing Zotero code
|
||||
*/
|
||||
Zotero.CiteprocRs = {
|
||||
init: async function () {
|
||||
if (Zotero.CiteprocRs.deferred) {
|
||||
return Zotero.CiteprocRs.deferred.promise;
|
||||
}
|
||||
Zotero.CiteprocRs.deferred = Zotero.Promise.defer();
|
||||
Zotero.debug("require('citeproc_rs_wasm')");
|
||||
// This is kind of nasty:
|
||||
// Due to Zotero being based on an old Firefox platform we are unable to use
|
||||
// the commonJS modules generated by citeproc_rs because they use too
|
||||
// advanced features.
|
||||
// Cormac has decided to provide a custom build just for Zotero
|
||||
// where citeproc_rs_wasm_include.js attaches some classes to Zotero.CiteprocRs.
|
||||
// This works fine until we call resetDB() in the test runner (or any other time we
|
||||
// could call ZoteroContext.reinit(). A usual require() call returns exported symbols
|
||||
// from some Gecko script cache. Unfortunately that means that the necessary citeproc
|
||||
// classes do not get reattached to the Zotero object and the Citeproc Wasm driver breaks
|
||||
// when trying to create its own objects (e.g. Zotero.CiteprocRs.WasmResult).
|
||||
// If we try to load citeproc_rs_wasm_include as a subscript we get errors about
|
||||
// let WasmResult; lines, i.e. a redeclaration of a symbol.
|
||||
// So we have to load this script and provide it a custom context with just a
|
||||
// Zotero symbol so that things don't break. Obviously this isn't optimal.
|
||||
// This might break with an upgrade to a higher Firefox version, but perhaps then we'll
|
||||
// be able to load the normal citeproc-rs CommonJS build.
|
||||
Components.classes["@mozilla.org/moz/jssubscript-loader;1"]
|
||||
.getService(Components.interfaces.mozIJSSubScriptLoader)
|
||||
.loadSubScript('resource://zotero/citeproc_rs_wasm_include.js',
|
||||
{ Zotero });
|
||||
// require('citeproc_rs_wasm_include');
|
||||
const CiteprocRs = require('citeproc_rs_wasm');
|
||||
// Initialize the wasm code
|
||||
Zotero.debug("Loading citeproc-rs wasm binary");
|
||||
const xhr = await Zotero.HTTP.request('GET', 'resource://zotero/citeproc_rs_wasm_bg.wasm', {
|
||||
responseType: "arraybuffer"
|
||||
});
|
||||
Zotero.debug("Initializing the CiteprocRs wasm driver");
|
||||
await CiteprocRs(xhr.response);
|
||||
Zotero.debug("CiteprocRs driver initialized successfully");
|
||||
this.Driver = CiteprocRs.Driver;
|
||||
Zotero.CiteprocRs.deferred.resolve();
|
||||
},
|
||||
|
||||
Engine: class {
|
||||
constructor(system, style, styleXML, locale, format, overrideLocale=false) {
|
||||
this._styleXML = styleXML;
|
||||
this._overrideLocale = overrideLocale;
|
||||
|
||||
this.locale = locale;
|
||||
|
||||
this._format = format;
|
||||
this._resetDriver();
|
||||
|
||||
this.styleID = style.styleID;
|
||||
this.hasBibliography = style._hasBibliography;
|
||||
this.sys = system;
|
||||
this.opt = { sort_citations: true };
|
||||
}
|
||||
|
||||
free(ignoreErrors=false) {
|
||||
try {
|
||||
Zotero.debug('CiteprocRs: free Driver', 5);
|
||||
this._driver.free();
|
||||
}
|
||||
catch (e) {
|
||||
if (ignoreErrors) return;
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
_resetDriver() {
|
||||
if (this._driver) {
|
||||
Zotero.debug('CiteprocRs: free Driver', 5);
|
||||
this._driver.free();
|
||||
}
|
||||
Zotero.debug('CiteprocRs: new Driver', 5);
|
||||
let options = {
|
||||
style: this._styleXML,
|
||||
format: this._format,
|
||||
fetcher: { fetchLocale: this._fetchLocale.bind(this) },
|
||||
}
|
||||
if (this._overrideLocale) {
|
||||
options.localeOverride = this.locale;
|
||||
}
|
||||
this._driver = new Zotero.CiteprocRs.Driver(options);
|
||||
}
|
||||
|
||||
async _fetchLocale(lang) {
|
||||
return Zotero.Cite.System.prototype.retrieveLocale(lang);
|
||||
}
|
||||
|
||||
setOutputFormat(format) {
|
||||
if (format == 'text') format = 'plain';
|
||||
if (this._format != format) {
|
||||
this._format = format;
|
||||
this._driver.setOutputFormat(format);
|
||||
}
|
||||
}
|
||||
|
||||
_insertCitationReferences(citation) {
|
||||
let cites = [];
|
||||
for (const citationItem of citation.citationItems) {
|
||||
let citeprocItem = this.sys.retrieveItem(citationItem.id);
|
||||
citeprocItem.id = `${citeprocItem.id}`;
|
||||
Zotero.debug(`CiteprocRs: insertReference ${JSON.stringify(citeprocItem)}`, 5);
|
||||
this._driver.insertReference(citeprocItem);
|
||||
let cite = { id: `${citeprocItem.id}`, locator: undefined, locators: undefined };
|
||||
additionalCSLProperties.forEach((key) => {
|
||||
if (!citationItem[key]) return;
|
||||
switch (key) {
|
||||
case 'suppress-author':
|
||||
cite['mode'] = "SuppressAuthor";
|
||||
break;
|
||||
case 'author-only':
|
||||
cite['mode'] = "AuthorOnly";
|
||||
break;
|
||||
default:
|
||||
cite[key] = citationItem[key];
|
||||
}
|
||||
});
|
||||
cites.push(cite);
|
||||
}
|
||||
return cites;
|
||||
}
|
||||
|
||||
_getClusterOrder(citations) {
|
||||
let clusters = [];
|
||||
for (let [citationID, noteIndex] of citations) {
|
||||
let cluster = { id: citationID };
|
||||
noteIndex = typeof noteIndex == "string" ? parseInt(noteIndex) : noteIndex;
|
||||
if (noteIndex) {
|
||||
cluster.note = noteIndex;
|
||||
}
|
||||
clusters.push(cluster);
|
||||
}
|
||||
return clusters;
|
||||
}
|
||||
|
||||
previewCitationCluster(citation, citationsPre, citationsPost, outputFormat) {
|
||||
if (!citation.citationID) citation.citationID = Zotero.Utilities.randomString(10);
|
||||
|
||||
let cites = this._insertCitationReferences(citation);
|
||||
let cluster = { cites };
|
||||
|
||||
let thisClusterOrder = {};
|
||||
let noteIndex = citation.properties.noteIndex;
|
||||
noteIndex = typeof noteIndex == "string" ? parseInt(noteIndex) : noteIndex;
|
||||
if (noteIndex) {
|
||||
thisClusterOrder.note = noteIndex;
|
||||
}
|
||||
let allClusterOrder = this._getClusterOrder(citationsPre.concat(citationsPost));
|
||||
allClusterOrder.splice(citationsPre.length, 0, thisClusterOrder);
|
||||
|
||||
Zotero.debug(`CiteprocRs: previewCluster ${JSON.stringify([cluster, allClusterOrder, outputFormat])}`, 5);
|
||||
return this._driver.previewCluster(cluster, allClusterOrder, outputFormat);
|
||||
}
|
||||
|
||||
// This is an undocumented citeproc-js endpoint that is used by Zotero from way back
|
||||
// when generating citations for clipboard or export.
|
||||
appendCitationCluster(citation) {
|
||||
this.insertCluster(citation);
|
||||
Zotero.debug(`CiteprocRs: builtCluster ${citation.citationID}`, 5);
|
||||
return this._driver.builtCluster(citation.citationID);
|
||||
}
|
||||
|
||||
insertCluster(citation) {
|
||||
let cluster = { id: citation.citationID };
|
||||
cluster.cites = this._insertCitationReferences(citation);
|
||||
|
||||
Zotero.debug(`CiteprocRs: insertCluster ${JSON.stringify(cluster)}`, 5);
|
||||
this._driver.insertCluster(cluster);
|
||||
return cluster;
|
||||
}
|
||||
|
||||
setClusterOrder(citations) {
|
||||
let clusters = this._getClusterOrder(citations);
|
||||
Zotero.debug(`CiteprocRs: setClusterOrder ${JSON.stringify(clusters)}`, 5);
|
||||
this._driver.setClusterOrder(clusters);
|
||||
}
|
||||
|
||||
getBatchedUpdates() {
|
||||
Zotero.debug(`CiteprocRs: batchedUpdates`, 5);
|
||||
return this._driver.batchedUpdates();
|
||||
}
|
||||
|
||||
rebuildProcessorState(citations, format, uncited) {
|
||||
this._format = format;
|
||||
this._resetDriver();
|
||||
for (let citation of citations) {
|
||||
this.insertCluster(citation);
|
||||
}
|
||||
this.setClusterOrder(citations.map(
|
||||
citation => [citation.citationID, citation.properties.noteIndex]));
|
||||
this.updateUncitedItems(uncited);
|
||||
}
|
||||
|
||||
updateUncitedItems(itemIDs) {
|
||||
let referenceIDs = [];
|
||||
for (let id of itemIDs) {
|
||||
let citeprocItem = this.sys.retrieveItem(id);
|
||||
citeprocItem.id = `${citeprocItem.id}`;
|
||||
referenceIDs.push(citeprocItem.id);
|
||||
Zotero.debug(`CiteprocRs: insertReference ${JSON.stringify(citeprocItem)}`, 5);
|
||||
this._driver.insertReference(citeprocItem);
|
||||
}
|
||||
Zotero.debug(`CiteprocRs: includeUncitedItems ${JSON.stringify(referenceIDs)}`);
|
||||
this._driver.includeUncited({ Specific: referenceIDs });
|
||||
}
|
||||
|
||||
updateItems(itemIDs) {
|
||||
Zotero.debug('CiteprocRstoJs: updateItems [forwarding to updateUncitedItems()]');
|
||||
return this.updateUncitedItems(itemIDs);
|
||||
}
|
||||
|
||||
makeBibliography() {
|
||||
Zotero.debug(`CiteprocRs: bibliographyMeta`, 5);
|
||||
|
||||
// Converting from the wrongly documented citeproc-rs return format
|
||||
// to the poorly named citeproc-js format. Sigh.
|
||||
let bibliographyMeta = this._driver.bibliographyMeta();
|
||||
bibliographyMeta = Object.assign(bibliographyMeta, {
|
||||
maxoffset: bibliographyMeta.maxOffset,
|
||||
linespacing: bibliographyMeta.lineSpacing,
|
||||
entryspacing: bibliographyMeta.entrySpacing,
|
||||
hangingindent: bibliographyMeta.hangingIndent,
|
||||
bibstart: bibliographyMeta.formatMeta && bibliographyMeta.formatMeta.markupPre,
|
||||
bibend: bibliographyMeta.formatMeta && bibliographyMeta.formatMeta.markupPost,
|
||||
});
|
||||
bibliographyMeta['second-field-align'] = bibliographyMeta.secondFieldAlign;
|
||||
|
||||
Zotero.debug(`CiteprocRs: makeBibliography`, 5);
|
||||
const bibliographyEntries = this._driver.makeBibliography();
|
||||
// Crazy citeproc-js behavior here
|
||||
const entry_ids = bibliographyEntries.map(entry => [entry.id]);
|
||||
let strings;
|
||||
if (this._format == 'html') {
|
||||
strings = bibliographyEntries.map(entry => `<div class="csl-entry">${entry.value}</div>`);
|
||||
}
|
||||
else if (this._format == 'plain') {
|
||||
strings = bibliographyEntries.map(entry => `${entry.value}\n`);
|
||||
}
|
||||
else {
|
||||
strings = bibliographyEntries.map(entry => entry.value);
|
||||
}
|
||||
return [
|
||||
Object.assign({ entry_ids }, bibliographyMeta),
|
||||
strings
|
||||
];
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
})();
|
|
@ -263,7 +263,7 @@ Zotero.Integration = new function() {
|
|||
}
|
||||
catch (e) {
|
||||
if (!(e instanceof Zotero.Exception.UserCancelled)) {
|
||||
Zotero.Integration._handleCommandError(document, e);
|
||||
Zotero.Integration._handleCommandError(document, session, e);
|
||||
}
|
||||
else {
|
||||
if (session) {
|
||||
|
@ -318,7 +318,7 @@ Zotero.Integration = new function() {
|
|||
}
|
||||
};
|
||||
|
||||
this._handleCommandError = async function (document, e) {
|
||||
this._handleCommandError = async function (document, session, e) {
|
||||
try {
|
||||
const supportURL = "https://www.zotero.org/support/kb/debugging_broken_documents";
|
||||
var displayError;
|
||||
|
@ -370,6 +370,12 @@ Zotero.Integration = new function() {
|
|||
if (index == 1) {
|
||||
Zotero.launchURL(supportURL);
|
||||
}
|
||||
|
||||
// If the driver panicked we cannot reuse it
|
||||
if (e instanceof Zotero.CiteprocRs.CiteprocRsDriverError) {
|
||||
session.style.free(true);
|
||||
delete Zotero.Integration.sessions[session.id];
|
||||
}
|
||||
}
|
||||
finally {
|
||||
Zotero.logError(e);
|
||||
|
@ -1782,8 +1788,10 @@ Zotero.Integration.Session.prototype.setData = async function (data, resetStyle)
|
|||
await Zotero.Styles.init();
|
||||
var getStyle = Zotero.Styles.get(data.style.styleID);
|
||||
data.style.hasBibliography = getStyle.hasBibliography;
|
||||
this.style = getStyle.getCiteProc(data.style.locale, data.prefs.automaticJournalAbbreviations);
|
||||
this.style.setOutputFormat(this.outputFormat);
|
||||
if (this.style && this.style.free) {
|
||||
this.style.free();
|
||||
}
|
||||
this.style = getStyle.getCiteProc(data.style.locale, this.outputFormat, data.prefs.automaticJournalAbbreviations);
|
||||
this.styleClass = getStyle.class;
|
||||
// We're changing the citeproc instance, so we'll have to reinsert all citations into the registry
|
||||
this.reload = true;
|
||||
|
@ -2038,6 +2046,9 @@ Zotero.Integration.Session.prototype.getCiteprocLists = function() {
|
|||
* Updates the list of citations to be serialized to the document
|
||||
*/
|
||||
Zotero.Integration.Session.prototype._updateCitations = async function () {
|
||||
if (Zotero.Prefs.get('cite.useCiteprocRs')) {
|
||||
return this._updateCitationsCiteprocRs();
|
||||
}
|
||||
Zotero.debug("Integration: Indices of new citations");
|
||||
Zotero.debug(Object.keys(this.newIndices));
|
||||
Zotero.debug("Integration: Indices of updated citations");
|
||||
|
@ -2080,6 +2091,52 @@ Zotero.Integration.Session.prototype._updateCitations = async function () {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Updates the list of citations to be serialized to the document with citeproc-rs
|
||||
*/
|
||||
Zotero.Integration.Session.prototype._updateCitationsCiteprocRs = async function () {
|
||||
Zotero.debug("Integration: Indices of new citations");
|
||||
Zotero.debug(Object.keys(this.newIndices));
|
||||
Zotero.debug("Integration: Indices of updated citations");
|
||||
Zotero.debug(Object.keys(this.updateIndices));
|
||||
|
||||
for (let indexList of [this.newIndices, this.updateIndices]) {
|
||||
for (let index in indexList) {
|
||||
if (indexList == this.newIndices) {
|
||||
delete this.newIndices[index];
|
||||
delete this.updateIndices[index];
|
||||
}
|
||||
|
||||
var citation = this.citationsByIndex[index];
|
||||
citation = citation.toJSON();
|
||||
|
||||
Zotero.debug(`Integration: citeprocRs.insertCluster(${citation.toSource()})`);
|
||||
this.style.insertCluster(citation);
|
||||
}
|
||||
}
|
||||
|
||||
let citationIDToIndex = {};
|
||||
for (const key in this.citationsByIndex) {
|
||||
citationIDToIndex[this.citationsByIndex[key].citationID] = key;
|
||||
}
|
||||
|
||||
const citations = this.getCiteprocLists()[0];
|
||||
Zotero.debug("Integration: citeprocRs.setClusterOrder()");
|
||||
this.style.setClusterOrder(citations);
|
||||
Zotero.debug("Integration: citeprocRs.getBatchedUpdates()");
|
||||
const updateSummary = this.style.getBatchedUpdates();
|
||||
Zotero.debug("Integration: got UpdateSummary from citeprocRs");
|
||||
for (const [citationID, text] of updateSummary.clusters) {
|
||||
const index = citationIDToIndex[citationID];
|
||||
this.citationsByIndex[index].text = text;
|
||||
this.processIndices[index] = true;
|
||||
}
|
||||
|
||||
this.bibliographyHasChanged |= updateSummary.bibliography
|
||||
&& Object.keys(updateSummary.bibliography.updatedEntries).length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restores processor state from document, without requesting citation updates
|
||||
*/
|
||||
|
@ -2844,6 +2901,14 @@ Zotero.Integration.BibliographyField = class extends Zotero.Integration.Field {
|
|||
};
|
||||
|
||||
Zotero.Integration.Citation = class {
|
||||
static refreshEmbeddedData(itemData) {
|
||||
if (itemData.shortTitle) {
|
||||
itemData['title-short'] = itemData.shortTitle;
|
||||
delete itemData.shortTitle;
|
||||
}
|
||||
return itemData;
|
||||
}
|
||||
|
||||
constructor(citationField, data, noteIndex) {
|
||||
data = Object.assign({ citationItems: [], properties: {} }, data)
|
||||
this.citationID = data.citationID;
|
||||
|
@ -2914,6 +2979,7 @@ Zotero.Integration.Citation = class {
|
|||
// Use embedded item
|
||||
if (citationItem.itemData) {
|
||||
Zotero.debug(`Item ${JSON.stringify(citationItem.uris)} not in library. Using embedded data`);
|
||||
citationItem.itemData = Zotero.Integration.Citation.refreshEmbeddedData(citationItem.itemData);
|
||||
// add new embedded item
|
||||
var itemData = Zotero.Utilities.deepCopy(citationItem.itemData);
|
||||
|
||||
|
@ -3049,18 +3115,12 @@ Zotero.Integration.Citation = class {
|
|||
serializeCitationItem.id = citationItem.id;
|
||||
serializeCitationItem.uris = citationItem.uris;
|
||||
|
||||
// XXX For compatibility with Zotero 2.0; to be removed at a later date
|
||||
serializeCitationItem.uri = serializeCitationItem.uris;
|
||||
|
||||
// always store itemData, since we have no way to get it back otherwise
|
||||
serializeCitationItem.itemData = citationItem.itemData;
|
||||
} else {
|
||||
serializeCitationItem.id = citationItem.id;
|
||||
serializeCitationItem.uris = Zotero.Integration.currentSession.uriMap.getURIsForItemID(citationItem.id);
|
||||
|
||||
// XXX For compatibility with Zotero 2.0; to be removed at a later date
|
||||
serializeCitationItem.uri = serializeCitationItem.uris;
|
||||
|
||||
serializeCitationItem.itemData = Zotero.Integration.currentSession.style.sys.retrieveItem(citationItem.id);
|
||||
}
|
||||
|
||||
|
@ -3184,7 +3244,6 @@ Zotero.Integration.Bibliography = class {
|
|||
|
||||
Zotero.debug(`Integration: style.updateUncitedItems ${Array.from(this.uncitedItemIDs.values()).toSource()}`);
|
||||
citeproc.updateUncitedItems(Array.from(this.uncitedItemIDs.values()));
|
||||
citeproc.setOutputFormat(Zotero.Integration.currentSession.outputFormat);
|
||||
let bibliography = citeproc.makeBibliography();
|
||||
Zotero.Cite.removeFromBibliography(bibliography, this.omittedItemIDs);
|
||||
|
||||
|
|
|
@ -289,12 +289,15 @@ Zotero.QuickCopy = new function() {
|
|||
};
|
||||
var html = csl.previewCitationCluster(citation, [], [], "html");
|
||||
var text = csl.previewCitationCluster(citation, [], [], "text");
|
||||
csl.free();
|
||||
} else {
|
||||
var style = Zotero.Styles.get(format.id);
|
||||
var cslEngine = style.getCiteProc(locale);
|
||||
var cslEngine = style.getCiteProc(locale, 'html');
|
||||
var html = Zotero.Cite.makeFormattedBibliographyOrCitationList(cslEngine, items, "html");
|
||||
cslEngine = style.getCiteProc(locale);
|
||||
cslEngine.free();
|
||||
cslEngine = style.getCiteProc(locale, 'text');
|
||||
var text = Zotero.Cite.makeFormattedBibliographyOrCitationList(cslEngine, items, "text");
|
||||
cslEngine.free();
|
||||
}
|
||||
|
||||
return {text:(format.contentType == "html" ? html : text), html:html};
|
||||
|
|
|
@ -45,6 +45,8 @@ Zotero.Styles = new function() {
|
|||
* Initializes styles cache, loading metadata for styles into memory
|
||||
*/
|
||||
this.init = Zotero.Promise.coroutine(function* (options = {}) {
|
||||
yield Zotero.CiteprocRs.init();
|
||||
|
||||
// Wait until bundled files have been updated, except when this is called by the schema update
|
||||
// code itself
|
||||
if (!options.fromSchemaUpdate) {
|
||||
|
@ -682,15 +684,17 @@ Zotero.Style = function (style, path) {
|
|||
/**
|
||||
* Get a citeproc-js CSL.Engine instance
|
||||
* @param {String} locale Locale code
|
||||
* @param {String} format Output format one of [rtf, html, text]
|
||||
* @param {Boolean} automaticJournalAbbreviations Whether to automatically abbreviate titles
|
||||
*/
|
||||
Zotero.Style.prototype.getCiteProc = function(locale, automaticJournalAbbreviations) {
|
||||
Zotero.Style.prototype.getCiteProc = function(locale, format, automaticJournalAbbreviations) {
|
||||
if(!locale) {
|
||||
var locale = Zotero.locale;
|
||||
if(!locale) {
|
||||
var locale = 'en-US';
|
||||
}
|
||||
}
|
||||
format = format || 'text';
|
||||
|
||||
// APA and some similar styles capitalize the first word of subtitles
|
||||
var uppercaseSubtitlesRE = /^apa($|-)|^academy-of-management($|-)|^(freshwater-science)/;
|
||||
|
@ -757,7 +761,22 @@ Zotero.Style.prototype.getCiteProc = function(locale, automaticJournalAbbreviati
|
|||
}
|
||||
|
||||
try {
|
||||
var citeproc = new Zotero.CiteProc.CSL.Engine(
|
||||
var citeproc;
|
||||
if (Zotero.Prefs.get('cite.useCiteprocRs')) {
|
||||
citeproc = new Zotero.CiteprocRs.Engine(
|
||||
new Zotero.Cite.System({
|
||||
automaticJournalAbbreviations,
|
||||
uppercaseSubtitles: uppercaseSubtitles
|
||||
}),
|
||||
this,
|
||||
xml,
|
||||
locale,
|
||||
format == 'text' ? 'plain' : format,
|
||||
overrideLocale
|
||||
);
|
||||
}
|
||||
else {
|
||||
citeproc = new Zotero.CiteProc.CSL.Engine(
|
||||
new Zotero.Cite.System({
|
||||
automaticJournalAbbreviations,
|
||||
uppercaseSubtitles
|
||||
|
@ -766,10 +785,12 @@ Zotero.Style.prototype.getCiteProc = function(locale, automaticJournalAbbreviati
|
|||
locale,
|
||||
overrideLocale
|
||||
);
|
||||
|
||||
citeproc.setOutputFormat(format);
|
||||
citeproc.free = () => 0;
|
||||
citeproc.opt.development_extensions.wrap_url_and_doi = true;
|
||||
// Don't try to parse author names. We parse them in itemToCSLJSON
|
||||
citeproc.opt.development_extensions.parse_names = false;
|
||||
}
|
||||
|
||||
return citeproc;
|
||||
} catch(e) {
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 10ffb4a766ce7d43aac3b321863a4e585669be02
|
||||
Subproject commit 9390fa2a4fd749688f2cf78e8a62a716643f4b90
|
|
@ -70,6 +70,7 @@ const xpcomFilesLocal = [
|
|||
'api',
|
||||
'attachments',
|
||||
'cite',
|
||||
'citeprocRsBridge',
|
||||
'cookieSandbox',
|
||||
'data/library',
|
||||
'data/libraries',
|
||||
|
|
|
@ -116,6 +116,7 @@ pref("extensions.zotero.export.bibliographySettings", "save-as-rtf");
|
|||
pref("extensions.zotero.export.displayCharsetOption", true);
|
||||
pref("extensions.zotero.export.citePaperJournalArticleURL", false);
|
||||
pref("extensions.zotero.cite.automaticJournalAbbreviations", true);
|
||||
pref("extensions.zotero.cite.useCiteprocRs", false);
|
||||
pref("extensions.zotero.import.charset", "auto");
|
||||
pref("extensions.zotero.import.createNewCollection.fromFileOpenHandler", true);
|
||||
pref("extensions.zotero.rtfScan.lastInputFile", "");
|
||||
|
|
6963
package-lock.json
generated
6963
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -15,6 +15,7 @@
|
|||
},
|
||||
"license": "",
|
||||
"dependencies": {
|
||||
"@citeproc-rs/wasm": "^0.2.0",
|
||||
"ace-builds": "^1.4.12",
|
||||
"bluebird": "^3.5.1",
|
||||
"classnames": "^2.2.6",
|
||||
|
|
1
resource/citeproc_rs_wasm.js
Symbolic link
1
resource/citeproc_rs_wasm.js
Symbolic link
|
@ -0,0 +1 @@
|
|||
../node_modules/@citeproc-rs/wasm/_zotero/citeproc_rs_wasm.js
|
1
resource/citeproc_rs_wasm_bg.wasm
Symbolic link
1
resource/citeproc_rs_wasm_bg.wasm
Symbolic link
|
@ -0,0 +1 @@
|
|||
../node_modules/@citeproc-rs/wasm/_zotero/citeproc_rs_wasm_bg.wasm
|
1
resource/citeproc_rs_wasm_include.js
Symbolic link
1
resource/citeproc_rs_wasm_include.js
Symbolic link
|
@ -0,0 +1 @@
|
|||
../node_modules/@citeproc-rs/wasm/_zotero/citeproc_rs_wasm_include.js
|
|
@ -1,7 +1,7 @@
|
|||
'use strict';
|
||||
|
||||
var require = (function() {
|
||||
var win, Zotero;
|
||||
var win, cons, Zotero;
|
||||
Components.utils.import('resource://zotero/loader.jsm');
|
||||
var requirer = Module('/', '/');
|
||||
var _runningTimers = {};
|
||||
|
@ -72,7 +72,6 @@ var require = (function() {
|
|||
return Zotero || {};
|
||||
}
|
||||
|
||||
var cons;
|
||||
if (typeof win.console !== 'undefined') {
|
||||
cons = console;
|
||||
}
|
||||
|
@ -82,6 +81,9 @@ var require = (function() {
|
|||
cons[key] = text => {getZotero(); typeof Zotero !== 'undefined' && false && Zotero.debug(`console.${key}: ${text}`)};
|
||||
}
|
||||
}
|
||||
if (!win.console) {
|
||||
win.console = cons;
|
||||
}
|
||||
let globals = {
|
||||
window: win,
|
||||
document: typeof win.document !== 'undefined' && win.document || {},
|
||||
|
@ -90,7 +92,9 @@ var require = (function() {
|
|||
setTimeout: win.setTimeout,
|
||||
clearTimeout: win.clearTimeout,
|
||||
requestAnimationFrame: win.setTimeout,
|
||||
cancelAnimationFrame: win.clearTimeout
|
||||
cancelAnimationFrame: win.clearTimeout,
|
||||
TextEncoder: TextEncoder,
|
||||
TextDecoder: TextDecoder,
|
||||
};
|
||||
Object.defineProperty(globals, 'Zotero', { get: getZotero });
|
||||
var loader = Loader({
|
||||
|
|
|
@ -47,7 +47,6 @@ async function babelWorker(ev) {
|
|||
.replace('document.body.appendChild(scrollDiv)', 'document.documentElement.appendChild(scrollDiv)')
|
||||
.replace('document.body.removeChild(scrollDiv)', 'document.documentElement.removeChild(scrollDiv)');
|
||||
}
|
||||
|
||||
// Patch single-file
|
||||
else if (sourcefile === 'resource/SingleFile/dist/single-file.js') {
|
||||
// Change for what I assume is a bug in Firefox. We create a singlefile
|
||||
|
|
|
@ -53,7 +53,8 @@ const symlinkFiles = [
|
|||
// Feed *.idl files are for documentation only
|
||||
'!resource/feeds/*.idl',
|
||||
'update.rdf',
|
||||
'!chrome/skin/default/zotero/**/*.scss'
|
||||
'!chrome/skin/default/zotero/**/*.scss',
|
||||
'!resource/citeproc_rs_wasm.js',
|
||||
];
|
||||
|
||||
|
||||
|
@ -108,7 +109,8 @@ const jsFiles = [
|
|||
'resource/react.js',
|
||||
'resource/react-dom.js',
|
||||
'resource/react-virtualized.js',
|
||||
'resource/SingleFile/dist/single-file.js'
|
||||
'resource/SingleFile/dist/single-file.js',
|
||||
'resource/citeproc_rs_wasm.js',
|
||||
];
|
||||
|
||||
const scssFiles = [
|
||||
|
|
170
test/tests/citeprocRsBridgeTest.js
Normal file
170
test/tests/citeprocRsBridgeTest.js
Normal file
|
@ -0,0 +1,170 @@
|
|||
"use strict";
|
||||
|
||||
describe("Zotero.CiteprocRs", function () {
|
||||
var chicagoNoteStyleID = "http://www.zotero.org/styles/chicago-note-bibliography";
|
||||
var chicagoAuthorDateStyleID = "http://www.zotero.org/styles/chicago-author-date";
|
||||
var style;
|
||||
function getCiteprocJSEngine(style) {
|
||||
Zotero.Prefs.set('cite.useCiteprocRs', false);
|
||||
return style.getCiteProc('en-US', 'text');
|
||||
}
|
||||
function getCiteprocRSEngine(style) {
|
||||
Zotero.Prefs.set('cite.useCiteprocRs', true);
|
||||
return style.getCiteProc('en-US', 'text');
|
||||
}
|
||||
function createCitationItem(item) {
|
||||
return {
|
||||
id: item.id,
|
||||
uris: Zotero.Utilities.randomString(10),
|
||||
itemData: Zotero.Cite.System.prototype.retrieveItem(item.id)
|
||||
};
|
||||
}
|
||||
function createACitation(items) {
|
||||
if (!Array.isArray(items)) items = [items];
|
||||
return {
|
||||
citationID: Zotero.Utilities.randomString(10),
|
||||
citationItems: items.map(createCitationItem),
|
||||
properties: { noteIndex: noteIndex++ }
|
||||
};
|
||||
}
|
||||
function assertProducedCitationsAreEqual(citation) {
|
||||
citeprocRS.insertCluster(citation);
|
||||
citeprocRS.setClusterOrder([[citation.citationID, citation.properties.noteIndex]]);
|
||||
let updateSummary = citeprocRS.getBatchedUpdates();
|
||||
let CRSCitation = updateSummary.clusters[0][1];
|
||||
let CRSBibl = Zotero.Cite.makeFormattedBibliography(citeprocRS, 'text');
|
||||
|
||||
// We need to deepcopy before passing to citeproc-js, because it doesn't respect
|
||||
// our objects and just writes stuff all over them.
|
||||
let [_, citationInfo] = citeprocJS.processCitationCluster(citation, [], []);
|
||||
let CJSCitation = citationInfo[0][1];
|
||||
let CJSBibl = Zotero.Cite.makeFormattedBibliography(citeprocJS, 'text');
|
||||
|
||||
Zotero.debug(`\nciteproc-js: ${CJSCitation}\nciteproc-rs: ${CRSCitation}`, 2);
|
||||
Zotero.debug(`\nciteproc-js: ${CJSBibl}\nciteproc-rs: ${CRSBibl}`, 2);
|
||||
|
||||
assert.equal(CJSCitation, CRSCitation, 'citations are equal');
|
||||
assert.deepEqual(CJSBibl, CRSBibl, 'bibliographies are equal');
|
||||
}
|
||||
|
||||
var item1, item2SameLastName, item3;
|
||||
var citeprocRS, citeprocJS;
|
||||
var noteIndex = 1;
|
||||
|
||||
before(async function () {
|
||||
await Zotero.Styles.init();
|
||||
item1 = createUnsavedDataObject(
|
||||
'item',
|
||||
{
|
||||
itemType: 'book',
|
||||
title: 'Test book'
|
||||
}
|
||||
);
|
||||
item1.libraryID = Zotero.Libraries.userLibraryID;
|
||||
item1.setField('date', '2021');
|
||||
item1.setCreators([
|
||||
{
|
||||
firstName: "First1",
|
||||
lastName: "Last1",
|
||||
creatorType: "author"
|
||||
}
|
||||
]);
|
||||
|
||||
item2SameLastName = item1.clone();
|
||||
item2SameLastName.setField('title', 'Test book 2');
|
||||
item2SameLastName.setCreators([
|
||||
{
|
||||
firstName: "DifferentFirst2",
|
||||
lastName: "Last1",
|
||||
creatorType: "author"
|
||||
}
|
||||
]);
|
||||
item3 = item1.clone();
|
||||
item3.setCreators([
|
||||
{
|
||||
firstName: "First3",
|
||||
lastName: "Last3",
|
||||
creatorType: "author"
|
||||
}
|
||||
]);
|
||||
await Zotero.Promise.all([item1.saveTx(), item2SameLastName.saveTx(), item3.saveTx()]);
|
||||
});
|
||||
|
||||
after(function () {
|
||||
Zotero.Prefs.set('cite.useCiteprocRs', false);
|
||||
});
|
||||
|
||||
beforeEach(function () {
|
||||
noteIndex = 1;
|
||||
citeprocJS = getCiteprocJSEngine(style);
|
||||
citeprocRS = getCiteprocRSEngine(style);
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
citeprocJS.free();
|
||||
citeprocRS.free();
|
||||
});
|
||||
|
||||
describe('with chicago-note-bibliography.csl', function () {
|
||||
before(function () {
|
||||
style = Zotero.Styles.get(chicagoNoteStyleID);
|
||||
});
|
||||
|
||||
it("should produce a correct citation", function () {
|
||||
assertProducedCitationsAreEqual(createACitation([item1, item3]));
|
||||
});
|
||||
|
||||
it("should produce a correct citation with a locator", function () {
|
||||
let citation = createACitation(item1);
|
||||
Object.assign(citation.citationItems[0], { locator: 1, label: "page" });
|
||||
assertProducedCitationsAreEqual(citation);
|
||||
});
|
||||
|
||||
it("should produce a correct citation with a prefix", function () {
|
||||
let citation = createACitation(item1);
|
||||
Object.assign(citation.citationItems[0], { prefix: 'hello' });
|
||||
assertProducedCitationsAreEqual(citation);
|
||||
});
|
||||
|
||||
it("should produce a correct citation with a suppressed author", function () {
|
||||
let citation = createACitation(item1);
|
||||
Object.assign(citation.citationItems[0], { 'suppress-author': true });
|
||||
assertProducedCitationsAreEqual(citation);
|
||||
});
|
||||
|
||||
it("should should produce ibid when appropriate", function () {
|
||||
let citation1 = createACitation(item1);
|
||||
let citation2 = createACitation(item1);
|
||||
|
||||
citeprocRS.insertCluster(citation1);
|
||||
citeprocRS.insertCluster(citation2);
|
||||
citeprocRS.setClusterOrder([[citation1.citationID, 1], [citation2.citationID, 2]]);
|
||||
let updateSummary = citeprocRS.getBatchedUpdates();
|
||||
|
||||
let [_, citationInfo] = citeprocJS.processCitationCluster(citation1, [], []);
|
||||
[_, citationInfo] = citeprocJS.processCitationCluster(citation2, [[citation1.citationID, 1]], []);
|
||||
|
||||
for (let i = 0; i < 2; i++) {
|
||||
let CRSCitation = updateSummary.clusters[i][1];
|
||||
let CJSCitation = citationInfo[i][1];
|
||||
Zotero.debug(`\nciteproc-js: ${CJSCitation}\nciteproc-rs: ${CRSCitation}`, 2);
|
||||
assert.equal(CJSCitation, CRSCitation, `citations #${i} are equal`);
|
||||
}
|
||||
|
||||
let CRSBibl = Zotero.Cite.makeFormattedBibliography(citeprocRS, 'text');
|
||||
let CJSBibl = Zotero.Cite.makeFormattedBibliography(citeprocJS, 'text');
|
||||
assert.deepEqual(CJSBibl, CRSBibl, 'bibliographies are equal');
|
||||
});
|
||||
});
|
||||
|
||||
// Chicago note-bibliography does not perform last name disambiguation
|
||||
describe('with chicago-author-date.csl', function () {
|
||||
before(function () {
|
||||
style = Zotero.Styles.get(chicagoAuthorDateStyleID);
|
||||
});
|
||||
|
||||
it("should perform last name disambiguation", function () {
|
||||
assertProducedCitationsAreEqual(createACitation([item1, item2SameLastName]));
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Reference in a new issue