citeproc-rs support (#2220)

Disabled under zotero.cite.useCiteprocRs by default
This commit is contained in:
Adomas Ven 2021-12-16 00:28:41 +02:00 committed by GitHub
parent 55e8f7914b
commit 44b6cd0525
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 7429 additions and 182 deletions

View file

@ -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");
}

View file

@ -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

View file

@ -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();
}

View file

@ -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"/>

View file

@ -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;
}

View file

@ -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;

View 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
];
}
},
};
})();

View file

@ -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);

View file

@ -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};

View file

@ -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

View file

@ -70,6 +70,7 @@ const xpcomFilesLocal = [
'api',
'attachments',
'cite',
'citeprocRsBridge',
'cookieSandbox',
'data/library',
'data/libraries',

View file

@ -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

File diff suppressed because it is too large Load diff

View file

@ -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",

View file

@ -0,0 +1 @@
../node_modules/@citeproc-rs/wasm/_zotero/citeproc_rs_wasm.js

View file

@ -0,0 +1 @@
../node_modules/@citeproc-rs/wasm/_zotero/citeproc_rs_wasm_bg.wasm

View file

@ -0,0 +1 @@
../node_modules/@citeproc-rs/wasm/_zotero/citeproc_rs_wasm_include.js

View file

@ -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({

View file

@ -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

View file

@ -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 = [

View 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]));
});
});
});