zotero/chrome/content/zotero/xpcom/integration.js

2336 lines
74 KiB
JavaScript
Raw Normal View History

/*
***** BEGIN LICENSE BLOCK *****
2009-12-28 09:47:49 +00:00
Copyright © 2009 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
2009-12-28 09:47:49 +00:00
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.
2009-12-28 09:47:49 +00:00
You should have received a copy of the GNU Affero General Public License
2009-12-28 09:47:49 +00:00
along with Zotero. If not, see <http://www.gnu.org/licenses/>.
***** END LICENSE BLOCK *****
*/
const RESELECT_KEY_URI = 1;
const RESELECT_KEY_ITEM_KEY = 2;
const RESELECT_KEY_ITEM_ID = 3;
const DATA_VERSION = 3;
2007-10-23 07:11:59 +00:00
// this is used only for update checking
const INTEGRATION_PLUGINS = ["zoteroMacWordIntegration@zotero.org",
"zoteroOpenOfficeIntegration@zotero.org", "zoteroWinWordIntegration@zotero.org"];
Zotero.Integration = new function() {
const INTEGRATION_MIN_VERSIONS = ["3.1.6.SVN", "3.5b1.SVN", "3.1.2.SVN"];
var _fifoFile = null;
var _tmpFile = null;
var _osascriptFile;
var _inProgress = false;
var _integrationVersionsOK = null;
// these need to be global because of GC
var _updateTimer;
this.sessions = {};
2008-11-30 20:18:48 +00:00
/**
* Initializes the pipe used for integration on non-Windows platforms.
*/
this.init = function() {
// We only use an integration pipe on OS X.
// On Linux, we use the alternative communication method in the OOo plug-in
// On Windows, we use a command line handler for integration. See
// components/zotero-integration-service.js for this implementation.
if(!Zotero.isMac) return;
// Determine where to put the pipe
// on OS X, first try /Users/Shared for those who can't put pipes in their home
// directories
_fifoFile = Components.classes["@mozilla.org/file/local;1"].
createInstance(Components.interfaces.nsILocalFile);
_fifoFile.initWithPath("/Users/Shared");
if(_fifoFile.exists() && _fifoFile.isDirectory() && _fifoFile.isWritable()) {
var logname = Components.classes["@mozilla.org/process/environment;1"].
getService(Components.interfaces.nsIEnvironment).
get("LOGNAME");
_fifoFile.append(".zoteroIntegrationPipe_"+logname);
} else {
_fifoFile = null;
}
if(!_fifoFile) {
// on other platforms, or as a fallback, use home directory
_fifoFile = Components.classes["@mozilla.org/file/directory_service;1"].
getService(Components.interfaces.nsIProperties).
get("Home", Components.interfaces.nsIFile);
_fifoFile.append(".zoteroIntegrationPipe");
}
// destroy old pipe, if one exists
try {
if(_fifoFile.exists()) {
Zotero.IPC.safePipeWrite(_fifoFile, "Zotero shutdown\n");
_fifoFile.remove(false);
}
} catch (e) {
// if pipe can't be deleted, log an error
Zotero.debug("Error removing old integration pipe", 1);
Zotero.logError(e);
Components.utils.reportError(
"Zotero word processor integration initialization failed. "
+ "See http://forums.zotero.org/discussion/12054/#Item_10 "
+ "for instructions on correcting this problem."
);
// can attempt to delete on OS X
try {
var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
.getService(Components.interfaces.nsIPromptService);
var deletePipe = promptService.confirm(null, Zotero.getString("integration.error.title"), Zotero.getString("integration.error.deletePipe"));
if(!deletePipe) return;
let escapedFifoFile = _fifoFile.path.replace("'", "'\\''");
_executeAppleScript("do shell script \"rmdir '"+escapedFifoFile+"'; rm -f '"+escapedFifoFile+"'\" with administrator privileges", true);
if(_fifoFile.exists()) return;
} catch(e) {
Zotero.logError(e);
return;
}
}
// try to initialize pipe
try {
Zotero.IPC.Pipe.initPipeListener(_fifoFile, _parseIntegrationPipeCommand);
} catch(e) {
Zotero.logError(e);
}
_updateTimer = Components.classes["@mozilla.org/timer;1"].
createInstance(Components.interfaces.nsITimer);
_updateTimer.initWithCallback({"notify":_checkPluginVersions}, 1000,
Components.interfaces.nsITimer.TYPE_ONE_SHOT);
}
function _checkPluginVersions() {
if(_updateTimer) _updateTimer = undefined;
var verComp = Components.classes["@mozilla.org/xpcom/version-comparator;1"]
.getService(Components.interfaces.nsIVersionComparator);
var addonsChecked = false;
var success = true;
function _checkAddons(addons) {
addonsChecked = true;
for(var i in addons) {
var addon = addons[i];
if(!addon) continue;
if(addon.userDisabled) continue;
if(verComp.compare(INTEGRATION_MIN_VERSIONS[i], addon.version) > 0) {
_integrationVersionsOK = false;
Zotero.Integration.activate();
var msg = Zotero.getString(
"integration.error.incompatibleVersion2",
[Zotero.version, addon.name, INTEGRATION_MIN_VERSIONS[i]]
);
Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
.getService(Components.interfaces.nsIPromptService)
.alert(null, Zotero.getString("integration.error.title"), msg);
success = false;
throw msg;
}
}
_integrationVersionsOK = true;
}
if(Zotero.isFx4) {
Components.utils.import("resource://gre/modules/AddonManager.jsm");
AddonManager.getAddonsByIDs(INTEGRATION_PLUGINS, _checkAddons);
while(!addonsChecked) Zotero.mainThread.processNextEvent(true);
} else {
var extMan = Components.classes['@mozilla.org/extensions/manager;1'].
getService(Components.interfaces.nsIExtensionManager);
_checkAddons([extMan.getItemForID(id) for each(id in INTEGRATION_PLUGINS)]);
}
return success;
}
/**
* Executes an integration command, first checking to make sure that versions are compatible
*/
this.execCommand = function execCommand(agent, command, docId) {
if(_inProgress) {
Zotero.Integration.activate();
Zotero.debug("Integration: Request already in progress; not executing "+agent+" "+command);
return;
}
_inProgress = true;
// Check integration component versions
if(_checkPluginVersions()) {
_callIntegration(agent, command, docId);
} else {
inProgress = false;
}
}
/**
* Parses a command received from the integration pipe
*/
function _parseIntegrationPipeCommand(string) {
if(string != "") {
// exec command if possible
var parts = string.match(/^([^ \n]*) ([^ \n]*)(?: ([^\n]*))?\n?$/);
if(parts) {
var agent = parts[1].toString();
var cmd = parts[2].toString();
var document = parts[3] ? parts[3].toString() : null;
Zotero.Integration.execCommand(agent, cmd, document);
} else {
Components.utils.reportError("Zotero: Invalid integration input received: "+string);
}
}
}
/**
* Calls the Integration applicatoon
*/
function _callIntegration(agent, command, docId) {
2009-08-25 07:02:24 +00:00
// Try to load the appropriate Zotero component; otherwise display an error using the alert
// service
try {
var componentClass = "@zotero.org/Zotero/integration/application?agent="+agent+";1";
2010-10-31 05:19:43 +00:00
Zotero.debug("Integration: Instantiating "+componentClass+" for command "+command+(docId ? " with doc "+docId : ""));
2009-08-25 07:02:24 +00:00
var application = Components.classes[componentClass]
.getService(Components.interfaces.zoteroIntegrationApplication);
} catch(e) {
_inProgress = false;
Zotero.Integration.activate();
2009-08-25 07:02:24 +00:00
Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
.getService(Components.interfaces.nsIPromptService)
.alert(null, Zotero.getString("integration.error.title"),
Zotero.getString("integration.error.notInstalled"));
throw e;
}
// Try to execute the command; otherwise display an error in alert service or word processor
// (depending on what is possible)
var integration, document;
try {
document = (application.getDocument && docId ? application.getDocument(docId) : application.getActiveDocument());
integration = new Zotero.Integration.Document(application, document);
integration[command]();
integration.cleanup();
} catch(e) {
if(integration) {
try {
integration.cleanup();
} catch(e) {
Components.utils.reportError(e);
}
}
if(!(e instanceof Zotero.Integration.UserCancelledException)) {
try {
var displayError = null;
if(e instanceof Zotero.Integration.DisplayException) {
displayError = e.toString();
} else {
// check to see whether there's a pyxpcom error in the console, since it doesn't
// get thrown directly
var message = "";
var consoleService = Components.classes["@mozilla.org/consoleservice;1"]
.getService(Components.interfaces.nsIConsoleService);
var messages = {};
consoleService.getMessageArray(messages, {});
messages = messages.value;
if(messages && messages.length) {
var lastMessage = messages[messages.length-1];
try {
var error = lastMessage.QueryInterface(Components.interfaces.nsIScriptError);
} catch(e2) {
if(lastMessage.message && lastMessage.message.substr(0, 12) == "ERROR:xpcom:") {
// print just the last line of the message, but re-throw the rest
message = lastMessage.message.substr(0, lastMessage.message.length-1);
message = "\n"+message.substr(message.lastIndexOf("\n"))
}
}
}
if(!message && typeof(e) == "object" && e.message) message = "\n\n"+e.message;
if(message != "\n\nExceptionAlreadyDisplayed") {
displayError = Zotero.getString("integration.error.generic")+message;
}
Zotero.debug(e);
}
if(displayError) {
if(integration) {
integration._doc.displayAlert(displayError,
Components.interfaces.zoteroIntegrationDocument.DIALOG_ICON_STOP,
Components.interfaces.zoteroIntegrationDocument.DIALOG_BUTTONS_OK);
} else {
Zotero.Integration.activate();
Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
.getService(Components.interfaces.nsIPromptService)
.alert(null, Zotero.getString("integration.error.title"), displayError);
}
}
} finally {
2009-08-25 07:02:24 +00:00
throw e;
}
}
} finally {
_inProgress = false;
}
}
/**
* Activates Firefox
*/
this.activate = function(win) {
if(Zotero.isMac) {
const BUNDLE_IDS = {
"Zotero":"org.zotero.zotero",
"Firefox":"org.mozilla.firefox",
"Minefield":"org.mozilla.minefield"
};
if(Zotero.isFx4 && win) {
const carbon = ctypes.open("/System/Library/Frameworks/Carbon.framework/Carbon");
/*
* struct ProcessSerialNumber {
* unsigned long highLongOfPSN;
* unsigned long lowLongOfPSN;
* };
*/
const ProcessSerialNumber = new ctypes.StructType("ProcessSerialNumber",
[{"highLongOfPSN":ctypes.uint32_t}, {"lowLongOfPSN":ctypes.uint32_t}]);
/*
* OSStatus SetFrontProcessWithOptions (
* const ProcessSerialNumber *inProcess,
* OptionBits inOptions
* );
*/
const SetFrontProcessWithOptions = carbon.declare("SetFrontProcessWithOptions",
ctypes.default_abi, ctypes.int32_t, ProcessSerialNumber.ptr, ctypes.uint32_t);
var psn = new ProcessSerialNumber();
psn.highLongOfPSN = 0;
psn.lowLongOfPSN = 2 // kCurrentProcess
win.addEventListener("load", function() {
var res = SetFrontProcessWithOptions(
psn.address(),
1 // kSetFrontProcessFrontWindowOnly = (1 << 0)
);
carbon.close();
}, false);
} else {
if(Zotero.oscpu == "PPC Mac OS X 10.4" || Zotero.oscpu == "Intel Mac OS X 10.4"
|| !BUNDLE_IDS[Zotero.appName]) {
// 10.4 doesn't support "tell application id"
_executeAppleScript('tell application "'+Zotero.appName+'" to activate');
} else {
_executeAppleScript('tell application id "'+BUNDLE_IDS[Zotero.appName]+'" to activate');
}
}
}
}
/**
* Runs an AppleScript on OS X
*
* @param script {String}
* @param block {Boolean} Whether the script should block until the process is finished.
*/
function _executeAppleScript(script, block) {
if(_osascriptFile === undefined) {
_osascriptFile = Components.classes["@mozilla.org/file/local;1"].
createInstance(Components.interfaces.nsILocalFile);
_osascriptFile.initWithPath("/usr/bin/osascript");
if(!_osascriptFile.exists()) _osascriptFile = false;
}
if(_osascriptFile) {
var proc = Components.classes["@mozilla.org/process/util;1"].
createInstance(Components.interfaces.nsIProcess);
proc.init(_osascriptFile);
try {
proc.run(!!block, ['-e', script], 2);
} catch(e) {}
}
}
}
/**
* An exception thrown when a document contains an item that no longer exists in the current document.
*
* @param reselectKeys {Array} Keys representing the missing item
* @param reselectKeyType {Integer} The type of the keys (see RESELECT_KEY_* constants)
* @param citationIndex {Integer} The index of the missing item within the citation cluster
* @param citationLength {Integer} The number of items cited in this citation cluster
*/
Zotero.Integration.MissingItemException = function(reselectKeys, reselectKeyType, citationIndex, citationLength) {
this.reselectKeys = reselectKeys;
this.reselectKeyType = reselectKeyType;
this.citationIndex = citationIndex;
this.citationLength = citationLength;
}
Zotero.Integration.MissingItemException.prototype.name = "MissingItemException";
Zotero.Integration.MissingItemException.prototype.message = "An item in this document is missing from your Zotero library.";
2009-08-25 07:02:24 +00:00
Zotero.Integration.MissingItemException.prototype.toString = function() { return this.message; };
Zotero.Integration.UserCancelledException = function() {};
Zotero.Integration.UserCancelledException.prototype.name = "UserCancelledException";
Zotero.Integration.UserCancelledException.prototype.message = "User cancelled document update.";
2009-08-25 07:02:24 +00:00
Zotero.Integration.UserCancelledException.prototype.toString = function() { return this.message; };
Zotero.Integration.DisplayException = function(name, params) {
this.name = name;
this.params = params ? params : [];
};
Zotero.Integration.DisplayException.prototype.toString = function() { return Zotero.getString("integration.error."+this.name, this.params); };
Zotero.Integration.CorruptFieldException = function(corruptFieldString) {
this.corruptFieldString = corruptFieldString;
}
Zotero.Integration.CorruptFieldException.prototype.name = "CorruptFieldException";
Zotero.Integration.CorruptFieldException.prototype.message = "A field code in this document is corrupted.";
Zotero.Integration.CorruptFieldException.prototype.toString = function() { return this.message+" "+this.corruptFieldString.toSource(); }
const INTEGRATION_TYPE_ITEM = 1;
const INTEGRATION_TYPE_BIBLIOGRAPHY = 2;
const INTEGRATION_TYPE_TEMP = 3;
// Placeholder for an empty bibliography
const BIBLIOGRAPHY_PLACEHOLDER = "{Bibliography}";
/**
* All methods for interacting with a document
* @constructor
*/
Zotero.Integration.Document = function(app, doc) {
this._app = app;
this._doc = doc;
}
/**
* Gets the type of the field
*/
Zotero.Integration.Document.prototype._getCodeTypeAndContent = function(rawCode) {
for each(var code in ["ITEM", "CITATION"]) {
if(rawCode.substr(0, code.length) === code) {
return [INTEGRATION_TYPE_ITEM, rawCode.substr(code.length+1)];
}
}
if(rawCode.substr(0, 4) === "BIBL") {
return [INTEGRATION_TYPE_BIBLIOGRAPHY, rawCode.substr(5)];
}
if(rawCode.substr(0, 4) === "TEMP") {
return [INTEGRATION_TYPE_TEMP, rawCode.substr(5)];
}
return [null, rawCode];
}
/**
* Creates a new session
* @param data {Zotero.Integration.DocumentData} Document data for new session
*/
Zotero.Integration.Document.prototype._createNewSession = function(data) {
data.sessionID = Zotero.randomString();
var session = Zotero.Integration.sessions[data.sessionID] = new Zotero.Integration.Session();
return session;
}
/**
* Gets preferences for a document
* @param require {Boolean} Whether an error should be thrown if no preferences or fields exist
* (otherwise, the set doc prefs dialog is shown)
* @param dontRunSetDocPrefs {Boolean} Whether to show the Set Document Preferences window if no
* preferences exist
*/
Zotero.Integration.Document.prototype._getSession = function(require, dontRunSetDocPrefs) {
this._reloadSession = false;
var dataString = this._doc.getDocumentData();
if(!dataString) {
var haveFields = false;
var data = new Zotero.Integration.DocumentData();
if(require) {
// check to see if fields already exist
for each(var fieldType in [this._app.primaryFieldType, this._app.secondaryFieldType]) {
var fields = this._doc.getFields(this._app.primaryFieldType);
if(fields.hasMoreElements()) {
data.prefs.fieldType = this._app.primaryFieldType;
haveFields = true;
break;
}
}
// if no fields, throw an error
if(!haveFields) {
throw new Zotero.Integration.DisplayException("mustInsertCitation");
} else {
Zotero.debug("Integration: No document preferences found, but found "+data.prefs.fieldType+" fields");
2009-08-25 07:02:24 +00:00
}
}
// Set doc prefs if no data string yet
this._session = this._createNewSession(data);
this._session.setData(data);
if(dontRunSetDocPrefs) return false;
try {
var ret = this._session.setDocPrefs(this._app.primaryFieldType, this._app.secondaryFieldType);
} finally {
this._doc.activate();
}
// save doc prefs in doc
this._doc.setDocumentData(this._session.data.serializeXML());
if(haveFields) {
this._reloadSession = true;
}
} else {
var data = new Zotero.Integration.DocumentData(dataString);
if(data.dataVersion < DATA_VERSION) {
if(data.dataVersion == 1
&& data.prefs.fieldType == "Field"
&& this._app.primaryFieldType == "ReferenceMark") {
// Converted OOo docs use ReferenceMarks, not fields
data.prefs.fieldType = "ReferenceMark";
}
var warning = this._doc.displayAlert(Zotero.getString("integration.upgradeWarning"),
Components.interfaces.zoteroIntegrationDocument.DIALOG_ICON_WARNING,
Components.interfaces.zoteroIntegrationDocument.DIALOG_BUTTONS_OK_CANCEL);
if(!warning) throw new Zotero.Integration.UserCancelledException();
} else if(data.dataVersion > DATA_VERSION) {
throw new Zotero.Integration.DisplayException("newerDocumentVersion", [data.zoteroVersion, Zotero.version]);
}
if(data.prefs.fieldType !== this._app.primaryFieldType
&& data.prefs.fieldType !== this._app.secondaryFieldType) {
throw new Zotero.Integration.DisplayException("fieldTypeMismatch");
}
if(Zotero.Integration.sessions[data.sessionID]) {
this._session = Zotero.Integration.sessions[data.sessionID];
} else {
this._session = this._createNewSession(data);
try {
this._session.setData(data);
} catch(e) {
// make sure style is defined
if(e instanceof Zotero.Integration.DisplayException && e.name === "invalidStyle") {
try {
this._session.setDocPrefs(this._app.primaryFieldType, this._app.secondaryFieldType);
} finally {
this._doc.activate();
}
} else {
throw e;
2009-08-25 07:02:24 +00:00
}
}
this._doc.setDocumentData(this._session.data.serializeXML());
this._reloadSession = true;
}
}
this._session.resetRequest(this);
2009-08-25 07:02:24 +00:00
return !!dataString;
}
/**
* Gets all fields for a document
* @param require {Boolean} Whether an error should be thrown if no fields exist
* @param dontRunSetDocPrefs {Boolean} Whether to show the Set Document Preferences window if no
* preferences exist
*/
Zotero.Integration.Document.prototype._getFields = function(require, dontRunSetDocPrefs) {
2009-08-25 07:02:24 +00:00
if(this._fields) return;
if(!this._session && !this._getSession(require, dontRunSetDocPrefs)) return;
var getFieldsTime = (new Date()).getTime();
var fields = this._doc.getFields(this._session.data.prefs['fieldType']);
this._fields = [];
while(fields.hasMoreElements()) {
this._fields.push(fields.getNext().QueryInterface(Components.interfaces.zoteroIntegrationField));
}
var endTime = (new Date()).getTime();
if(Zotero.Debug.enabled) {
Zotero.debug("Integration: got "+this._fields.length+" fields in "+
(endTime-getFieldsTime)/1000+"; "+
1000/((endTime-getFieldsTime)/this._fields.length)+" fields/second");
}
if(require && !this._fields.length) {
2009-08-25 07:02:24 +00:00
throw new Zotero.Integration.DisplayException("mustInsertCitation");
}
2009-08-25 07:02:24 +00:00
return;
}
/**
* Checks that it is appropriate to add fields to the current document at the current
* positon, then adds one.
*/
Zotero.Integration.Document.prototype._addField = function(note) {
// Get citation types if necessary
if(!this._doc.canInsertField(this._session.data.prefs['fieldType'])) {
2009-08-25 07:02:24 +00:00
throw new Zotero.Integration.DisplayException("cannotInsertHere");
return false;
}
var field = this._doc.cursorInField(this._session.data.prefs['fieldType']);
if(field) {
if(!this._doc.displayAlert(Zotero.getString("integration.replace"),
Components.interfaces.zoteroIntegrationDocument.DIALOG_ICON_STOP,
Components.interfaces.zoteroIntegrationDocument.DIALOG_BUTTONS_OK_CANCEL)) return false;
}
2007-10-23 07:11:59 +00:00
if(!field) {
var field = this._doc.insertField(this._session.data.prefs['fieldType'],
(note ? this._session.data.prefs["noteType"] : 0));
}
return field;
}
/**
* Shows an error if a field code is corrupted
* @param {Exception} e The exception thrown
* @param {Field} field The Zotero field object
* @param {Integer} i The field index
*/
Zotero.Integration.Document.prototype._showCorruptFieldError = function(e, field, i) {
var msg = Zotero.getString("integration.corruptField")+'\n\n'+
Zotero.getString('integration.corruptField.description');
field.select();
var result = this._doc.displayAlert(msg,
Components.interfaces.zoteroIntegrationDocument.DIALOG_ICON_CAUTION,
Components.interfaces.zoteroIntegrationDocument.DIALOG_BUTTONS_YES_NO_CANCEL);
if(result == 0) {
throw e;
} else if(result == 1) { // No
this._removeCodeFields.push(i);
} else {
// Display reselect edit citation dialog
var added = this._session.editCitation(i, field.getNoteIndex());
if(added) {
this._doc.activate();
} else {
throw new Zotero.Integration.UserCancelledException();
}
}
}
/**
* Loads existing citations and bibliographies out of a document, and creates or edits fields
*/
2009-08-31 04:50:22 +00:00
Zotero.Integration.Document.prototype._updateSession = function(newField, editField) {
var deleteKeys = {};
this._deleteFields = [];
this._removeCodeFields = [];
this._bibliographyFields = [];
var bibliographyData = "";
// first collect entire bibliography
this._getFields();
var editFieldIndex = false;
2009-08-31 04:50:22 +00:00
var collectFieldsTime = (new Date()).getTime();
for(var i in this._fields) {
var field = this._fields[i];
if(editField && field.equals(editField)) {
editFieldIndex = i;
} else {
try {
var fieldCode = field.getCode();
} catch(e) {
this._showCorruptFieldError(e, field, i);
}
var [type, content] = this._getCodeTypeAndContent(fieldCode);
if(type === INTEGRATION_TYPE_ITEM) {
var noteIndex = (this._session.styleClass == "note" ? field.getNoteIndex() : 0);
try {
this._session.addCitation(i, noteIndex, content);
} catch(e) {
if(e instanceof Zotero.Integration.MissingItemException) {
// First, check if we've already decided to remove field codes from these
var reselect = true;
for each(var reselectKey in e.reselectKeys) {
if(deleteKeys[reselectKey]) {
this._removeCodeFields.push(i);
reselect = false;
break;
}
}
if(reselect) {
// Ask user what to do with this item
if(e.citationLength == 1) {
var msg = Zotero.getString("integration.missingItem.single");
} else {
var msg = Zotero.getString("integration.missingItem.multiple", (e.citationIndex+1).toString());
}
msg += '\n\n'+Zotero.getString('integration.missingItem.description');
field.select();
var result = this._doc.displayAlert(msg, 1, 3);
if(result == 0) { // Cancel
throw new Zotero.Integration.UserCancelledException();
} else if(result == 1) { // No
for each(var reselectKey in e.reselectKeys) {
deleteKeys[reselectKey] = true;
}
this._removeCodeFields.push(i);
} else { // Yes
// Display reselect item dialog
this._session.reselectItem(e);
// Now try again
this._session.addCitation(i, field.getNoteIndex(), content);
this._doc.activate();
}
}
} else if(e instanceof Zotero.Integration.CorruptFieldException) {
this._showCorruptFieldError(e, field, i);
} else {
throw e;
}
}
} else if(type === INTEGRATION_TYPE_BIBLIOGRAPHY) {
this._bibliographyFields.push(field);
if(!this._session.bibliographyData && !bibliographyData) {
bibliographyData = content;
2009-08-31 04:50:22 +00:00
}
} else if(type === INTEGRATION_TYPE_TEMP) {
if(newField && newField.equals(field)) {
2009-08-31 04:50:22 +00:00
editFieldIndex = i;
editField = field;
} else {
this._deleteFields.push(i);
}
}
}
}
2009-08-31 04:50:22 +00:00
var endTime = (new Date()).getTime();
if(Zotero.Debug.enabled) {
Zotero.debug("Integration: collected "+this._fields.length+" fields in "+
(endTime-collectFieldsTime)/1000+"; "+
1000/((endTime-collectFieldsTime)/this._fields.length)+" fields/second");
}
// load uncited items from bibliography
if(bibliographyData && !this._session.bibliographyData) {
try {
this._session.loadBibliographyData(bibliographyData);
} catch(e) {
if(e instanceof Zotero.Integration.CorruptFieldException) {
var msg = Zotero.getString("integration.corruptBibliography")+'\n\n'+
Zotero.getString('integration.corruptBibliography.description');
var result = this._doc.displayAlert(msg,
Components.interfaces.zoteroIntegrationDocument.DIALOG_ICON_CAUTION,
Components.interfaces.zoteroIntegrationDocument.DIALOG_BUTTONS_OK_CANCEL);
if(result == 0) {
throw e;
} else {
bibliographyData = "";
this._session.bibliographyHasChanged = true;
this._session.bibliographyDataHasChanged = true;
}
} else {
throw e;
}
}
}
// if we are reloading this session, assume no item IDs to be updated except for edited items
if(this._reloadSession) {
//this._session.restoreProcessorState(); TODO doesn't appear to be working properly
this._session.updateUpdateIndices();
var deleteCitations = this._session.updateCitations();
this._deleteFields = this._deleteFields.concat([i for(i in deleteCitations)]);
this._session.updateIndices = {};
this._session.updateItemIDs = {};
this._session.bibliographyHasChanged = false;
}
// create new citation or edit existing citation
if(editFieldIndex) {
var [type, editFieldCode] = this._getCodeTypeAndContent(editField.getCode());
var editCitation = editFieldCode ? this._session.unserializeCitation(editFieldCode, editFieldIndex) : null;
var editNoteIndex = editField.getNoteIndex();
var added = this._session.editCitation(editFieldIndex, editNoteIndex, editCitation);
this._doc.activate();
if(!added) {
if(editFieldCode) { // cancelled editing; just add as if nothing happened
this._session.addCitation(editFieldIndex, editNoteIndex, editCitation);
} else { // cancelled creation; delete the citation
this._session.deleteCitation(editFieldIndex);
}
}
}
}
/**
* Updates bibliographies and fields within a document
2011-08-09 05:13:13 +00:00
* @param {Boolean} forceCitations Whether to regenerate all citations
* @param {Boolean} forceBibliography Whether to regenerate all bibliography entries
* @param {Boolean} [ignoreCitationChanges] Whether to ignore changes to citations that have been
* modified since they were created, instead of showing a warning
*/
2011-08-09 05:13:13 +00:00
Zotero.Integration.Document.prototype._updateDocument = function(forceCitations, forceBibliography,
ignoreCitationChanges) {
// update citations
2010-08-16 08:52:13 +00:00
this._session.updateUpdateIndices(forceCitations);
var deleteCitations = this._session.updateCitations();
this._deleteFields = this._deleteFields.concat([i for(i in deleteCitations)]);
for(var i in this._session.updateIndices) {
var citation = this._session.citationsByIndex[i];
var field = this._fields[i];
// If there is no citation, we're deleting it, or we shouldn't update it, ignore it
if(!citation || deleteCitations[i]) continue;
if(!citation.properties.dontUpdate) {
var isRich = false;
var formattedCitation = citation.properties.custom
? citation.properties.custom : this._session.citationText[i];
if(formattedCitation.indexOf("\\") !== -1) {
// need to set text as RTF
formattedCitation = "{\\rtf "+formattedCitation+"}"
isRich = true;
}
if(forceCitations || citation.properties.formattedCitation !== formattedCitation) {
// Check if citation has been manually modified
2011-08-09 05:13:13 +00:00
if(!ignoreCitationChanges && citation.properties.plainCitation) {
var plainCitation = field.getText();
if(plainCitation !== citation.properties.plainCitation) {
// Citation manually modified; ask user if they want to save changes
field.select();
var result = this._doc.displayAlert(
Zotero.getString("integration.citationChanged")+"\n\n"+Zotero.getString("integration.citationChanged.description"),
Components.interfaces.zoteroIntegrationDocument.DIALOG_ICON_CAUTION,
Components.interfaces.zoteroIntegrationDocument.DIALOG_BUTTONS_YES_NO);
if(result) {
citation.properties.dontUpdate = true;
}
}
}
if(!citation.properties.dontUpdate) {
field.setText(formattedCitation, isRich);
citation.properties.formattedCitation = formattedCitation;
citation.properties.plainCitation = field.getText();
}
}
}
var fieldCode = this._session.getCitationField(citation);
if(fieldCode != citation.properties.field) {
field.setCode(
(this._session.data.prefs.storeReferences ? "ITEM CSL_CITATION" : "ITEM")
+" "+fieldCode);
if(this._session.data.prefs.fieldType === "ReferenceMark" && isRich) {
// For ReferenceMarks with formatting, we need to set the text again, because
// setting the field code removes formatting from the mark. I don't like this.
field.setText(citation.properties.custom ? citation.properties.custom
: citation.properties.formattedCitation, isRich);
}
}
}
// update bibliographies
if(this._bibliographyFields.length // if blbliography exists
&& (this._session.bibliographyHasChanged // and bibliography changed
|| forceBibliography)) { // or if we should generate regardless of changes
if(forceBibliography || this._session.bibliographyDataHasChanged) {
var bibliographyData = this._session.getBibliographyData();
for each(var field in this._bibliographyFields) {
field.setCode("BIBL "+bibliographyData
+(this._session.data.prefs.storeReferences ? " CSL_BIBLIOGRAPHY" : ""));
}
}
// get bibliography and format as RTF
var bib = this._session.getBibliography();
update to citeproc-js 1.0.52 closes #1709: [patch] citeproc-js 1.0.51 compatibility patch: makeBibliography() return value closes #1712: [patch] citeproc-js 1.0.52 compatibility patch From Frank's notes for 1.0.52: In this release, the following change may require accomodation within the calling application: The processor quashes double spaces in the output. RTF control words can be terminated with a space, but this causes an immediately following space to be lost from the output. To avoid this, the RTF output mode of the processor has been adjusted to terminate RTF control words with {} rather than space. Applications that convert processor output between other formats (such as HTML) may need to be adjusted to accomodate this new behavior. Also in this release: Reverse the RTF-related conservatism in the quashing of duplicate spaces that was introduced in 1.0.51. Add missing parens to suspect test condition (not related to any known bug). From Frank's notes for 1.0.51: The API return value changes slightly with this version: For styles that have no bibliography element, the command makeBibliography() now returns false, rather than a stub bibliography object. Code adjustments in the calling application may be required to accommodate this change in behavior. In other bibliography and citation returns, the processor now provides new arrays, bibliography_errors and citation_errors respectively, with information on bibliography entries or cites that produce no meaningful rendered output. Details are in the processor manual: http://gsl-nagoya-u.net/http/pub/citeproc-doc.html#handling-items-with-no-rendered-form Also in this release: Fix string breakage affecting text-case="title". Fix over-aggressive quashing of spaces, which affected spacing following RTF entities.
2010-08-16 08:07:07 +00:00
var bibliographyText = "";
if(bib) {
bibliographyText = bib[0].bibstart+bib[1].join("\\\r\n")+"\\\r\n"+bib[0].bibend;
update to citeproc-js 1.0.52 closes #1709: [patch] citeproc-js 1.0.51 compatibility patch: makeBibliography() return value closes #1712: [patch] citeproc-js 1.0.52 compatibility patch From Frank's notes for 1.0.52: In this release, the following change may require accomodation within the calling application: The processor quashes double spaces in the output. RTF control words can be terminated with a space, but this causes an immediately following space to be lost from the output. To avoid this, the RTF output mode of the processor has been adjusted to terminate RTF control words with {} rather than space. Applications that convert processor output between other formats (such as HTML) may need to be adjusted to accomodate this new behavior. Also in this release: Reverse the RTF-related conservatism in the quashing of duplicate spaces that was introduced in 1.0.51. Add missing parens to suspect test condition (not related to any known bug). From Frank's notes for 1.0.51: The API return value changes slightly with this version: For styles that have no bibliography element, the command makeBibliography() now returns false, rather than a stub bibliography object. Code adjustments in the calling application may be required to accommodate this change in behavior. In other bibliography and citation returns, the processor now provides new arrays, bibliography_errors and citation_errors respectively, with information on bibliography entries or cites that produce no meaningful rendered output. Details are in the processor manual: http://gsl-nagoya-u.net/http/pub/citeproc-doc.html#handling-items-with-no-rendered-form Also in this release: Fix string breakage affecting text-case="title". Fix over-aggressive quashing of spaces, which affected spacing following RTF entities.
2010-08-16 08:07:07 +00:00
// if bibliography style not set, set it
2010-09-27 17:12:41 +00:00
if(!this._session.data.style.bibliographyStyleHasBeenSet) {
update to citeproc-js 1.0.52 closes #1709: [patch] citeproc-js 1.0.51 compatibility patch: makeBibliography() return value closes #1712: [patch] citeproc-js 1.0.52 compatibility patch From Frank's notes for 1.0.52: In this release, the following change may require accomodation within the calling application: The processor quashes double spaces in the output. RTF control words can be terminated with a space, but this causes an immediately following space to be lost from the output. To avoid this, the RTF output mode of the processor has been adjusted to terminate RTF control words with {} rather than space. Applications that convert processor output between other formats (such as HTML) may need to be adjusted to accomodate this new behavior. Also in this release: Reverse the RTF-related conservatism in the quashing of duplicate spaces that was introduced in 1.0.51. Add missing parens to suspect test condition (not related to any known bug). From Frank's notes for 1.0.51: The API return value changes slightly with this version: For styles that have no bibliography element, the command makeBibliography() now returns false, rather than a stub bibliography object. Code adjustments in the calling application may be required to accommodate this change in behavior. In other bibliography and citation returns, the processor now provides new arrays, bibliography_errors and citation_errors respectively, with information on bibliography entries or cites that produce no meaningful rendered output. Details are in the processor manual: http://gsl-nagoya-u.net/http/pub/citeproc-doc.html#handling-items-with-no-rendered-form Also in this release: Fix string breakage affecting text-case="title". Fix over-aggressive quashing of spaces, which affected spacing following RTF entities.
2010-08-16 08:07:07 +00:00
var bibStyle = Zotero.Cite.getBibliographyFormatParameters(bib);
// set bibliography style
this._doc.setBibliographyStyle(bibStyle.firstLineIndent, bibStyle.indent,
bibStyle.lineSpacing, bibStyle.entrySpacing, bibStyle.tabStops, bibStyle.tabStops.length);
// set bibliographyStyleHasBeenSet parameter to prevent further changes
2010-09-27 17:12:41 +00:00
this._session.data.style.bibliographyStyleHasBeenSet = true;
update to citeproc-js 1.0.52 closes #1709: [patch] citeproc-js 1.0.51 compatibility patch: makeBibliography() return value closes #1712: [patch] citeproc-js 1.0.52 compatibility patch From Frank's notes for 1.0.52: In this release, the following change may require accomodation within the calling application: The processor quashes double spaces in the output. RTF control words can be terminated with a space, but this causes an immediately following space to be lost from the output. To avoid this, the RTF output mode of the processor has been adjusted to terminate RTF control words with {} rather than space. Applications that convert processor output between other formats (such as HTML) may need to be adjusted to accomodate this new behavior. Also in this release: Reverse the RTF-related conservatism in the quashing of duplicate spaces that was introduced in 1.0.51. Add missing parens to suspect test condition (not related to any known bug). From Frank's notes for 1.0.51: The API return value changes slightly with this version: For styles that have no bibliography element, the command makeBibliography() now returns false, rather than a stub bibliography object. Code adjustments in the calling application may be required to accommodate this change in behavior. In other bibliography and citation returns, the processor now provides new arrays, bibliography_errors and citation_errors respectively, with information on bibliography entries or cites that produce no meaningful rendered output. Details are in the processor manual: http://gsl-nagoya-u.net/http/pub/citeproc-doc.html#handling-items-with-no-rendered-form Also in this release: Fix string breakage affecting text-case="title". Fix over-aggressive quashing of spaces, which affected spacing following RTF entities.
2010-08-16 08:07:07 +00:00
this._doc.setDocumentData(this._session.data.serializeXML());
}
}
// set bibliography text
for each(var field in this._bibliographyFields) {
if(bibliographyText) {
field.setText(bibliographyText, true);
} else {
field.setText("{Bibliography}", false);
}
}
}
// do this operations in reverse in case plug-ins care about order
2009-08-31 04:50:22 +00:00
this._deleteFields.sort();
for(var i=(this._deleteFields.length-1); i>=0; i--) {
this._fields[this._deleteFields[i]].delete();
}
2009-08-31 04:50:22 +00:00
this._removeCodeFields.sort();
for(var i=(this._removeCodeFields.length-1); i>=0; i--) {
this._fields[this._removeCodeFields[i]].removeCode();
}
}
/**
* Adds a citation to the current document.
*/
Zotero.Integration.Document.prototype.addCitation = function() {
2009-08-25 07:02:24 +00:00
this._getSession();
var field = this._addField(true);
if(!field) return;
field.setCode("TEMP");
this._updateSession(field);
this._updateDocument();
}
/**
* Edits the citation at the cursor position.
*/
Zotero.Integration.Document.prototype.editCitation = function() {
2009-08-25 07:02:24 +00:00
this._getSession(true);
var field = this._doc.cursorInField(this._session.data.prefs['fieldType'])
if(!field) {
2009-08-25 07:02:24 +00:00
throw new Zotero.Integration.DisplayException("notInCitation");
}
2009-08-31 04:50:22 +00:00
this._updateSession(false, field);
this._updateDocument(false, false);
}
/**
* Adds a bibliography to the current document.
*/
Zotero.Integration.Document.prototype.addBibliography = function() {
2009-08-25 07:02:24 +00:00
this._getSession(true);
// Make sure we can have a bibliography
if(!this._session.data.style.hasBibliography) {
2009-08-25 07:02:24 +00:00
throw new Zotero.Integration.DisplayException("noBibliography");
}
// Make sure we have some citations
2009-08-25 07:02:24 +00:00
this._getFields(true);
var field = this._addField();
if(!field) return;
var bibliographyData = this._session.getBibliographyData();
field.setCode("BIBL "+bibliographyData
+(this._session.data.prefs.storeReferences ? " CSL_BIBLIOGRAPHY" : ""));
this._fields.push(field);
this._updateSession();
this._updateDocument(false, true);
}
/**
* Edits bibliography metadata.
*/
Zotero.Integration.Document.prototype.editBibliography = function() {
// Make sure we have a bibliography
2009-08-25 07:02:24 +00:00
this._getFields(true);
var haveBibliography = false;
for(var i=this._fields.length-1; i>=0; i--) {
var code = this._fields[i].getCode();
var [type, content] = this._getCodeTypeAndContent(code);
if(type == INTEGRATION_TYPE_BIBLIOGRAPHY) {
haveBibliography = true;
break;
}
}
if(!haveBibliography) {
2009-08-25 07:02:24 +00:00
throw new Zotero.Integration.DisplayException("mustInsertBibliography");
}
this._updateSession();
this._session.editBibliography();
this._doc.activate();
this._updateDocument(false, true);
}
/**
* Updates the citation data for all citations and bibliography entries.
*/
Zotero.Integration.Document.prototype.refresh = function() {
2009-08-25 07:02:24 +00:00
this._getFields(true);
// Send request, forcing update of citations and bibliography
this._updateSession();
this._updateDocument(true, true);
}
/**
* Deletes field codes.
*/
Zotero.Integration.Document.prototype.removeCodes = function() {
2009-08-25 07:02:24 +00:00
this._getFields(true);
var result = this._doc.displayAlert(Zotero.getString("integration.removeCodesWarning"),
Components.interfaces.zoteroIntegrationDocument.DIALOG_ICON_WARNING,
Components.interfaces.zoteroIntegrationDocument.DIALOG_BUTTONS_OK_CANCEL);
if(result) {
for(var i=this._fields.length-1; i>=0; i--) {
this._fields[i].removeCode();
2007-10-23 07:11:59 +00:00
}
}
}
/**
* Displays a dialog to set document preferences (style, footnotes/endnotes, etc.)
*/
Zotero.Integration.Document.prototype.setDocPrefs = function() {
this._getFields(false, true);
2009-08-25 07:02:24 +00:00
try {
var oldData = this._session.setDocPrefs(this._app.primaryFieldType, this._app.secondaryFieldType);
} finally {
this._doc.activate();
}
if(oldData) {
this._doc.setDocumentData(this._session.data.serializeXML());
if(this._fields && this._fields.length) {
// if there are fields, we will have to convert some things; get a list of what we need to deal with
var convertBibliographies = oldData === true || oldData.prefs.fieldType != this._session.data.prefs.fieldType;
var convertItems = convertBibliographies || oldData.prefs.noteType != this._session.data.prefs.noteType;
var fieldsToConvert = new Array();
var fieldNoteTypes = new Array();
for each(var field in this._fields) {
var fieldCode = field.getCode();
var [type, content] = this._getCodeTypeAndContent(fieldCode);
if(convertItems && type === INTEGRATION_TYPE_ITEM) {
2011-08-09 05:13:13 +00:00
var citation = this._session.unserializeCitation(fieldCode);
if(!citation.properties.dontUpdate) {
fieldsToConvert.push(field);
fieldNoteTypes.push(this._session.data.prefs.noteType);
}
} else if(convertBibliographies && type === INTEGRATION_TYPE_BIBLIOGRAPHY) {
fieldsToConvert.push(field);
fieldNoteTypes.push(0);
}
}
if(fieldsToConvert.length) {
// pass to conversion function
this._doc.convert(new Zotero.Integration.Document.JSEnumerator(fieldsToConvert),
this._session.data.prefs.fieldType, fieldNoteTypes, fieldNoteTypes.length);
// clear fields so that they will get collected again before refresh
this._fields = undefined;
}
2007-10-23 07:11:59 +00:00
// refresh contents
2011-08-09 05:13:13 +00:00
this._getFields(true);
this._updateSession();
this._updateDocument(true, true, true);
2007-10-23 07:11:59 +00:00
}
}
}
/**
* Cleans up any changes made before returning, even if an error occurred
*/
Zotero.Integration.Document.prototype.cleanup = function() {
this._doc.cleanup()
}
/**
* An exceedingly simple nsISimpleEnumerator implementation
*/
Zotero.Integration.Document.JSEnumerator = function(objArray) {
this.objArray = objArray;
}
Zotero.Integration.Document.JSEnumerator.prototype.hasMoreElements = function() {
return this.objArray.length;
}
Zotero.Integration.Document.JSEnumerator.prototype.getNext = function() {
return this.objArray.shift();
}
/**
* Keeps track of all session-specific variables
2007-10-23 07:11:59 +00:00
*/
Zotero.Integration.Session = function() {
// holds items not in document that should be in bibliography
this.uncitedItems = {};
this.omittedItems = {};
this.customBibliographyText = {};
this.reselectedItems = {};
this.citationIDs = {};
}
/**
* Resets per-request variables in the CitationSet
*/
Zotero.Integration.Session.prototype.resetRequest = function(doc) {
this.citationsByItemID = {};
this.citationsByIndex = [];
this.embeddedItems = {};
this.embeddedItemsByURI = {};
this.uriMap = new Zotero.Integration.URIMap(this);
this.regenerateAll = false;
this.bibliographyHasChanged = false;
this.bibliographyDataHasChanged = false;
this.updateItemIDs = {};
this.updateIndices = {};
this.newIndices = {};
this.oldCitationIDs = this.citationIDs;
this.citationIDs = {};
this.citationText = {};
this.doc = doc;
}
/**
* Changes the Session style and data
* @param data {Zotero.Integration.DocumentData}
2007-10-23 07:11:59 +00:00
*/
Zotero.Integration.Session.prototype.setData = function(data) {
var oldStyleID = (this.data && this.data.style.styleID ? this.data.style.styleID : false);
this.data = data;
if(data.style.styleID && oldStyleID != data.style.styleID) {
this.styleID = data.style.styleID;
try {
var getStyle = Zotero.Styles.get(data.style.styleID);
data.style.hasBibliography = getStyle.hasBibliography;
this.style = getStyle.csl;
this.style.setOutputFormat("rtf");
this.styleClass = getStyle.class;
this.dateModified = new Object();
} catch(e) {
Zotero.logError(e);
data.style.styleID = undefined;
throw new Zotero.Integration.DisplayException("invalidStyle");
}
return true;
}
return false;
}
/**
* Displays a dialog in a modal-like fashion without hanging the thread
*/
Zotero.Integration.Session.prototype._displayDialog = function(url, options, io) {
if(this.doc) this.doc.cleanup();
var allOptions = 'chrome,centerscreen';
// without this, Firefox gets raised with our windows under Compiz
if(Zotero.isLinux) allOptions += ',dialog=no';
if(options) allOptions += ','+options;
var window = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
.getService(Components.interfaces.nsIWindowWatcher)
.openWindow(null, url, '', allOptions, (io ? io : null));
Zotero.Integration.activate(window);
while(!window.closed) {
Zotero.mainThread.processNextEvent(true);
if(window.newWindow) window = window.newWindow;
}
}
/**
* Displays a dialog to set document preferences
*/
Zotero.Integration.Session.prototype.setDocPrefs = function(primaryFieldType, secondaryFieldType) {
var io = new function() {
this.wrappedJSObject = this;
};
if(this.data) {
io.style = this.data.style.styleID;
io.useEndnotes = this.data.prefs.noteType == 0 ? 0 : this.data.prefs.noteType-1;
io.fieldType = this.data.prefs.fieldType;
io.primaryFieldType = primaryFieldType;
io.secondaryFieldType = secondaryFieldType;
io.storeReferences = this.data.prefs.storeReferences;
}
this._displayDialog('chrome://zotero/content/integration/integrationDocPrefs.xul', '', io);
2009-08-25 07:02:24 +00:00
if(!io.style) throw new Zotero.Integration.UserCancelledException();
// set data
var oldData = this.data;
var data = new Zotero.Integration.DocumentData();
data.sessionID = oldData.sessionID;
data.style.styleID = io.style;
data.prefs.fieldType = io.fieldType;
data.prefs.storeReferences = io.storeReferences;
this.setData(data);
// need to do this after setting the data so that we know if it's a note style
this.data.prefs.noteType = this.style && this.styleClass == "note" ? io.useEndnotes+1 : 0;
if(!oldData || oldData.style.styleID != data.style.styleID
|| oldData.prefs.noteType != data.prefs.noteType
|| oldData.prefs.fieldType != data.prefs.fieldType) {
this.oldCitationIDs = {};
}
return oldData ? oldData : true;
}
/**
* Reselects an item to replace a deleted item
* @param exception {Zotero.Integration.MissingItemException}
*/
Zotero.Integration.Session.prototype.reselectItem = function(exception) {
var io = new function() {
this.wrappedJSObject = this;
};
io.addBorder = Zotero.isWin;
io.singleSelection = true;
this._displayDialog('chrome://zotero/content/selectItemsDialog.xul', 'resizable', io);
if(io.dataOut && io.dataOut.length) {
var itemID = io.dataOut[0];
// add reselected item IDs to hash, so they can be used
for each(var reselectKey in exception.reselectKeys) {
this.reselectedItems[reselectKey] = itemID;
}
// add old URIs to map, so that they will be included
if(exception.reselectKeyType == RESELECT_KEY_URI) {
this.uriMap.add(itemID, exception.reselectKeys.concat(this.uriMap.getURIsForItemID(itemID)));
}
// flag for update
this.updateItemIDs[itemID] = true;
}
}
/**
* Generates a field from a citation object
2007-10-23 07:11:59 +00:00
*/
Zotero.Integration.Session.prototype.getCitationField = function(citation) {
const saveProperties = ["custom", "unsorted", "formattedCitation", "plainCitation", "dontUpdate"];
const saveCitationItemKeys = ["locator", "label", "suppress-author", "author-only", "prefix",
"suffix"];
var addSchema = false;
var type;
var field = [];
2007-10-23 07:11:59 +00:00
field.push('"citationID":'+uneval(citation.citationID));
var properties = JSON.stringify(citation.properties, saveProperties);
if(properties != "{}") {
field.push('"properties":'+properties);
2007-10-23 07:11:59 +00:00
}
var m = citation.citationItems.length;
var citationItems = new Array(m);
for(var j=0; j<m; j++) {
var citationItem = citation.citationItems[j],
serializeCitationItem = {},
key, value;
// add URI and itemData
var slashIndex;
if(typeof citationItem.id === "string" && (slashIndex = citationItem.id.indexOf("/")) !== -1) {
// this is an embedded item
serializeCitationItem.id = citationItem.itemData.id;
serializeCitationItem.uris = citationItem.uris;
// always store itemData, since we have no way to get it back otherwise
serializeCitationItem.itemData = citationItem.itemData;
addSchema = true;
} else {
serializeCitationItem.id = citationItem.id;
serializeCitationItem.uris = this.uriMap.getURIsForItemID(citationItem.id);
// XXX For compatibility with older versions of Zotero; to be removed at a later date
serializeCitationItem.uri = serializeCitationItem.uris;
// add itemData only if requested
if(this.data.prefs.storeReferences) {
serializeCitationItem.itemData = citationItem.item;
addSchema = true;
}
}
// copy saveCitationItemKeys
for(var i=0, n=saveCitationItemKeys.length; i<n; i++) {
if((value = serializeCitationItem[(key = saveCitationItemKeys[i])])) {
serializeCitationItem[key] = value;
}
}
citationItems[j] = JSON.stringify(serializeCitationItem);
2007-10-23 07:11:59 +00:00
}
field.push('"citationItems":['+citationItems.join(",")+"]");
2007-10-23 07:11:59 +00:00
if(addSchema) {
field.push('"schema":"https://github.com/citation-style-language/schema/raw/master/csl-citation.json"');
}
return "{"+field.join(",")+"}";
2007-10-23 07:11:59 +00:00
}
/**
* Adds a citation based on a serialized Word field
2007-10-23 07:11:59 +00:00
*/
Zotero.Integration._oldCitationLocatorMap = {
p:"page",
g:"paragraph",
l:"line"
2007-10-23 07:11:59 +00:00
};
/**
* Adds a citation to the arrays representing the document
2007-10-23 07:11:59 +00:00
*/
Zotero.Integration.Session.prototype.addCitation = function(index, noteIndex, arg) {
var index = parseInt(index, 10);
2007-10-23 07:11:59 +00:00
if(typeof(arg) == "string") { // text field
if(arg == "!" || arg == "X") return;
var citation = this.unserializeCitation(arg, index);
2007-10-23 07:11:59 +00:00
} else { // a citation already
var citation = arg;
}
// get items
for(var i=0, n=citation.citationItems.length; i<n; i++) {
var citationItem = citation.citationItems[i];
// get Zotero item
var zoteroItem = false;
if(citationItem.uris) {
[zoteroItem, needUpdate] = this.uriMap.getZoteroItemForURIs(citationItem.uris);
if(needUpdate) this.updateIndices[index] = true;
} else {
if(citationItem.key) {
zoteroItem = Zotero.Items.getByKey(citationItem.key);
} else if(citationItem.itemID) {
zoteroItem = Zotero.Items.get(citationItem.itemID);
} else if(citationItem.id) {
zoteroItem = Zotero.Items.get(citationItem.id);
}
if(zoteroItem) this.updateIndices[index] = true;
}
// if no item, check if it was already reselected and otherwise handle as a missing item
if(!zoteroItem) {
if(citationItem.uris) {
var reselectKeys = citationItem.uris;
var reselectKeyType = RESELECT_KEY_URI;
} else if(citationItem.key) {
var reselectKeys = [citationItem.key];
var reselectKeyType = RESELECT_KEY_ITEM_KEY;
} else if(citationItem.id) {
var reselectKeys = [citationItem.id];
var reselectKeyType = RESELECT_KEY_ITEM_ID;
} else {
var reselectKeys = [citationItem.itemID];
var reselectKeyType = RESELECT_KEY_ITEM_ID;
}
// look to see if item has already been reselected
for each(var reselectKey in reselectKeys) {
if(this.reselectedItems[reselectKey]) {
zoteroItem = Zotero.Items.get(this.reselectedItems[reselectKey]);
citationItem.id = zoteroItem.id;
this.updateIndices[index] = true;
break;
}
}
if(!zoteroItem) {
// check embedded items
if(citationItem.uris) {
var success = false;
for(var j=0, m=citationItem.uris.length; j<m; j++) {
var embeddedItem = this.embeddedItemsByURI[citationItem.uris[j]];
if(embeddedItem) {
citationItem.id = this.data.sessionID+"/"+embeddedItem.id;
success = true;
break;
}
}
if(success) continue;
}
if(citationItem.itemData) {
// add new embedded item
var itemData = Zotero.Utilities.deepCopy(citationItem.itemData);
for(var j=0, m=citationItem.uris.length; j<m; j++) {
this.embeddedItemsByURI[citationItem.uris[j]] = itemData;
}
// assign a random string as an item ID
var anonymousID = itemData.id = Zotero.randomString();
this.embeddedItems[anonymousID] = itemData;
citationItem.id = this.data.sessionID+"/"+anonymousID;
} else {
// if not already reselected, throw a MissingItemException
throw(new Zotero.Integration.MissingItemException(
reselectKeys, reselectKeyType, i, citation.citationItems.length));
}
}
}
if(zoteroItem) {
citationItem.id = zoteroItem.id;
}
}
citation.properties.added = true;
citation.properties.zoteroIndex = index;
citation.properties.noteIndex = noteIndex;
this.citationsByIndex[index] = citation;
// add to citationsByItemID and citationsByIndex
for(var i=0; i<citation.citationItems.length; i++) {
var citationItem = citation.citationItems[i];
if(!this.citationsByItemID[citationItem.id]) {
this.citationsByItemID[citationItem.id] = [citation];
this.bibliographyHasChanged = true;
} else {
var byItemID = this.citationsByItemID[citationItem.id];
if(byItemID[byItemID.length-1].properties.zoteroIndex < index) {
// if index is greater than the last index, add to end
byItemID.push(citation);
} else {
// otherwise, splice in at appropriate location
for(var j=0; byItemID[j].properties.zoteroIndex < index && j<byItemID.length-1; j++) {}
byItemID.splice(j++, 0, citation);
}
2007-10-23 07:11:59 +00:00
}
}
var needNewID = !citation.citationID || this.citationIDs[citation.citationID];
if(needNewID || !this.oldCitationIDs[citation.citationID]) {
if(needNewID) {
Zotero.debug("Integration: "+citation.citationID+" ("+index+") needs new citationID");
citation.citationID = Zotero.randomString();
}
this.newIndices[index] = true;
this.updateIndices[index] = true;
}
Zotero.debug("Integration: adding citationID "+citation.citationID);
this.citationIDs[citation.citationID] = true;
}
/**
* Unserializes a JSON citation into a citation object (sans items)
*/
Zotero.Integration.Session.prototype.unserializeCitation = function(arg, index) {
var firstBracket = arg.indexOf("{");
if(firstBracket !== -1) { // JSON field
arg = arg.substr(firstBracket);
// fix for corrupted fields
var lastBracket = arg.lastIndexOf("}");
if(lastBracket+1 != arg.length) {
this.updateIndices[index] = true;
arg = arg.substr(0, lastBracket+1);
}
2007-10-23 07:11:59 +00:00
// get JSON
try {
var citation = JSON.parse(arg);
} catch(e) {
// fix for corrupted fields (corrupted by Word, somehow)
try {
var citation = JSON.parse(arg.substr(0, arg.length-1));
} catch(e) {
// another fix for corrupted fields (corrupted by 2.1b1)
try {
var citation = JSON.parse(arg.replace(/{{((?:\s*,?"unsorted":(?:true|false)|\s*,?"custom":"(?:(?:\\")?[^"]*\s*)*")*)}}/, "{$1}"));
} catch(e) {
throw new Zotero.Integration.CorruptFieldException(arg);
}
}
}
2007-10-23 07:11:59 +00:00
// fix for uppercase citation codes
if(citation.CITATIONITEMS) {
this.updateIndices[index] = true;
citation.citationItems = [];
for (var i=0; i<citation.CITATIONITEMS.length; i++) {
for (var j in citation.CITATIONITEMS[i]) {
switch (j) {
case 'ITEMID':
var field = 'itemID';
break;
// 'position', 'custom'
default:
var field = j.toLowerCase();
}
if (!citation.citationItems[i]) {
citation.citationItems[i] = {};
}
citation.citationItems[i][field] = citation.CITATIONITEMS[i][j];
}
}
}
if(!citation.properties) citation.properties = {};
for each(var citationItem in citation.citationItems) {
// for upgrade from Zotero 2.0 or earlier
if(citationItem.locatorType) {
2011-03-30 19:53:30 +00:00
citationItem.label = citationItem.locatorType;
delete citationItem.locatorType;
} else if(citationItem.suppressAuthor) {
citationItem["suppress-author"] = citationItem["suppressAuthor"];
delete citationItem.suppressAuthor;
}
// fix for improper upgrade from Zotero 2.1 in <2.1.5
if(parseInt(citationItem.label) == citationItem.label) {
const locatorTypeTerms = ["page", "book", "chapter", "column", "figure", "folio",
"issue", "line", "note", "opus", "paragraph", "part", "section", "sub verbo",
"volume", "verse"];
citationItem.label = locatorTypeTerms[parseInt(citationItem.label)];
2007-10-23 07:11:59 +00:00
}
// for update from Zotero 2.1 or earlier
if(citationItem.uri) {
citationItem.uris = citationItem.uri;
delete citationItem.uri;
}
2007-10-23 07:11:59 +00:00
}
// for upgrade from Zotero 2.0 or earlier
if(citation.sort) {
citation.properties.unsorted = !citation.sort;
delete citation.sort;
}
if(citation.custom) {
citation.properties.custom = citation.custom;
delete citation.custom;
}
if(!citation.citationID) citation.citationID = Zotero.randomString();
citation.properties.field = arg;
2007-10-23 07:11:59 +00:00
} else { // ye olde style field
var underscoreIndex = arg.indexOf("_");
var itemIDs = arg.substr(0, underscoreIndex).split("|");
var lastIndex = arg.lastIndexOf("_");
if(lastIndex != underscoreIndex+1) {
var locatorString = arg.substr(underscoreIndex+1, lastIndex-underscoreIndex-1);
var locators = locatorString.split("|");
}
var citationItems = new Array();
for(var i=0; i<itemIDs.length; i++) {
var citationItem = {id:itemIDs[i]};
2007-10-23 07:11:59 +00:00
if(locators) {
citationItem.locator = locators[i].substr(1);
citationItem.label = Zotero.Integration._oldCitationLocatorMap[locators[i][0]];
2007-10-23 07:11:59 +00:00
}
citationItems.push(citationItem);
}
var citation = {"citationItems":citationItems, properties:{}};
this.updateIndices[index] = true;
2007-10-23 07:11:59 +00:00
}
return citation;
}
/**
* marks a citation for removal
*/
Zotero.Integration.Session.prototype.deleteCitation = function(index) {
var oldCitation = (this.citationsByIndex[index] ? this.citationsByIndex[index] : false);
2007-10-23 07:11:59 +00:00
this.citationsByIndex[index] = {properties:{"delete":true}};
if(oldCitation && oldCitation.citationItems & oldCitation.properties.added) {
// clear out old citations if necessary
for each(var citationItem in oldCitation.citationItems) {
if(this.citationsByItemID[citationItem.id]) {
var indexInItemID = this.citationsByItemID[citationItem.id].indexOf(oldCitation);
if(indexInItemID !== -1) {
this.citationsByItemID[citationItem.id] = this.citationsByItemID[citationItem.id].splice(indexInItemID, 1);
if(this.citationsByItemID[citationItem.id].length == 0) {
delete this.citationsByItemID[citationItem.id];
}
}
}
}
}
Zotero.debug("Integration: Deleting old citationID "+oldCitation.citationID);
if(oldCitation.citationID) delete this.citationIDs[oldCitation.citationID];
this.updateIndices[index] = true;
}
/**
* Gets integration bibliography
*/
Zotero.Integration.Session.prototype.getBibliography = function() {
this.updateUncitedItems();
// generate bibliography
var bib = this.style.makeBibliography();
update to citeproc-js 1.0.52 closes #1709: [patch] citeproc-js 1.0.51 compatibility patch: makeBibliography() return value closes #1712: [patch] citeproc-js 1.0.52 compatibility patch From Frank's notes for 1.0.52: In this release, the following change may require accomodation within the calling application: The processor quashes double spaces in the output. RTF control words can be terminated with a space, but this causes an immediately following space to be lost from the output. To avoid this, the RTF output mode of the processor has been adjusted to terminate RTF control words with {} rather than space. Applications that convert processor output between other formats (such as HTML) may need to be adjusted to accomodate this new behavior. Also in this release: Reverse the RTF-related conservatism in the quashing of duplicate spaces that was introduced in 1.0.51. Add missing parens to suspect test condition (not related to any known bug). From Frank's notes for 1.0.51: The API return value changes slightly with this version: For styles that have no bibliography element, the command makeBibliography() now returns false, rather than a stub bibliography object. Code adjustments in the calling application may be required to accommodate this change in behavior. In other bibliography and citation returns, the processor now provides new arrays, bibliography_errors and citation_errors respectively, with information on bibliography entries or cites that produce no meaningful rendered output. Details are in the processor manual: http://gsl-nagoya-u.net/http/pub/citeproc-doc.html#handling-items-with-no-rendered-form Also in this release: Fix string breakage affecting text-case="title". Fix over-aggressive quashing of spaces, which affected spacing following RTF entities.
2010-08-16 08:07:07 +00:00
if(bib) {
// omit items
Zotero.Cite.removeFromBibliography(bib, this.omittedItems);
// replace items with their custom counterpars
for(var i in bib[0].entry_ids) {
if(this.customBibliographyText[bib[0].entry_ids[i]]) {
bib[1][i] = this.customBibliographyText[bib[0].entry_ids[i]];
}
}
}
return bib;
}
/**
* Calls CSL.Engine.updateUncitedItems() to reconcile list of uncited items
2007-10-23 07:11:59 +00:00
*/
Zotero.Integration.Session.prototype.updateUncitedItems = function() {
// There appears to be a bug somewhere here.
if(Zotero.Debug.enabled) Zotero.debug("Integration: style.updateUncitedItems("+this.uncitedItems.toSource()+")");
this.style.updateUncitedItems([parseInt(i) for(i in this.uncitedItems)]);
2007-10-23 07:11:59 +00:00
}
/**
* Refreshes updateIndices variable to include fields for modified items
2007-10-23 07:11:59 +00:00
*/
Zotero.Integration.Session.prototype.updateUpdateIndices = function(regenerateAll) {
if(regenerateAll || this.regenerateAll) {
2007-10-23 07:11:59 +00:00
// update all indices
2011-03-03 07:09:08 +00:00
for(var i in this.citationsByIndex) {
this.newIndices[i] = true;
2007-10-23 07:11:59 +00:00
this.updateIndices[i] = true;
}
2007-10-23 07:11:59 +00:00
} else {
// update only item IDs
for(var i in this.updateItemIDs) {
if(this.citationsByItemID[i] && this.citationsByItemID[i].length) {
for(var j=0; j<this.citationsByItemID[i].length; j++) {
this.updateIndices[this.citationsByItemID[i][j].properties.zoteroIndex] = true;
}
}
}
2007-10-23 07:11:59 +00:00
}
}
- implement previewCitationCluster(), restoreProcessorState(), new "bibchange" flag in processCitationCluster() output, and new opt.sort_citations flag in word processor integration - upgrade to citeproc-js 1.0.29 From Frank's 1.0.25 announcement: Provide new command, previewCitationCluster(), that returns string for hypothetical citation at specified position, without affecting processor state. Fix bug that would have cause appendCitationCluster() to run updateItems() unnecessarily. Provide for forced generation of citationID, for internal use in previewing. From Frank's 1.0.26 announcement: Implement new command restoreProcessorState(), for use in, er, restoring the processor state, when position variables and citation sort keys are already known. From Frank's 1.0.27 announcement: This fixes a couple of obvious problems in the code of the new restoreProcessorState() command. From Frank's 1.0.28 announcement: This version introduces a significant change to the return value of processCitationCluster(). It is now an array with two elements, the first being a JS object that serves as a data segment, and the second the list of two-element arrays representing insertion indexes and strings for insertion (as previously documented). An API change of this scale probably calls for some more visible sign in the version numbering, but the original statement on versioning says that the major and minor numbers will align with the CSL schema, so we stay at level 1.0. The data segment referred to above contains just one element currently, "bibchange", which is true if processing the citation results in any change affecting the bibliography. This release also introduces one change and one addition to style configuration flags. The flag at citation.opt["citation-number-sort"] has been moved to opt.citation_number_sort, for clarity and consistency. A new flag, opt.sort_citations, is true if citations are sorted by the style in any way. From Frank's 1.0.29 announcement: Complete reimplementation of cite-level disambiguation. The new code is more compact and maintainable, and avoids thrashing behavior that afflicted the previous code when a large number of cites required both add-names and year-suffix disambiguation. Suppress year suffix when fresh ambig keys are generated. Inserts by a plugin affecting year suffixes should now be correctly handled.
2010-06-13 22:53:57 +00:00
/**
* Returns citations before and after a given index
*/
Zotero.Integration.Session.prototype._getPrePost = function(index) {
var citationIndices = [];
var citationsPre = [];
for(var i=0; i<index; i++) {
if(this.citationsByIndex[i] && !this.newIndices[i] && !this.citationsByIndex[i].properties.delete) {
citationsPre.push([this.citationsByIndex[i].citationID, this.citationsByIndex[i].properties.noteIndex]);
citationIndices.push(i);
}
}
citationIndices.push(index);
var citationsPost = [];
for(var i=index+1; i<this.citationsByIndex.length; i++) {
if(this.citationsByIndex[i] && !this.newIndices[i] && !this.citationsByIndex[i].properties.delete) {
citationsPost.push([this.citationsByIndex[i].citationID, this.citationsByIndex[i].properties.noteIndex]);
citationIndices.push(i);
}
}
return [citationsPre, citationsPost, citationIndices];
}
/**
* Returns a formatted citation
*/
Zotero.Integration.Session.prototype.formatCitation = function(index, citation) {
if(!this.citationText[index]) {
- implement previewCitationCluster(), restoreProcessorState(), new "bibchange" flag in processCitationCluster() output, and new opt.sort_citations flag in word processor integration - upgrade to citeproc-js 1.0.29 From Frank's 1.0.25 announcement: Provide new command, previewCitationCluster(), that returns string for hypothetical citation at specified position, without affecting processor state. Fix bug that would have cause appendCitationCluster() to run updateItems() unnecessarily. Provide for forced generation of citationID, for internal use in previewing. From Frank's 1.0.26 announcement: Implement new command restoreProcessorState(), for use in, er, restoring the processor state, when position variables and citation sort keys are already known. From Frank's 1.0.27 announcement: This fixes a couple of obvious problems in the code of the new restoreProcessorState() command. From Frank's 1.0.28 announcement: This version introduces a significant change to the return value of processCitationCluster(). It is now an array with two elements, the first being a JS object that serves as a data segment, and the second the list of two-element arrays representing insertion indexes and strings for insertion (as previously documented). An API change of this scale probably calls for some more visible sign in the version numbering, but the original statement on versioning says that the major and minor numbers will align with the CSL schema, so we stay at level 1.0. The data segment referred to above contains just one element currently, "bibchange", which is true if processing the citation results in any change affecting the bibliography. This release also introduces one change and one addition to style configuration flags. The flag at citation.opt["citation-number-sort"] has been moved to opt.citation_number_sort, for clarity and consistency. A new flag, opt.sort_citations, is true if citations are sorted by the style in any way. From Frank's 1.0.29 announcement: Complete reimplementation of cite-level disambiguation. The new code is more compact and maintainable, and avoids thrashing behavior that afflicted the previous code when a large number of cites required both add-names and year-suffix disambiguation. Suppress year suffix when fresh ambig keys are generated. Inserts by a plugin affecting year suffixes should now be correctly handled.
2010-06-13 22:53:57 +00:00
var citationsPre, citationsPost, citationIndices;
[citationsPre, citationsPost, citationIndices] = this._getPrePost(index);
2011-03-29 00:57:10 +00:00
if(Zotero.Debug.enabled) {
Zotero.debug("Integration: style.processCitationCluster("+citation.toSource()+", "+citationsPre.toSource()+", "+citationsPost.toSource());
}
var newCitations = this.style.processCitationCluster(citation, citationsPre, citationsPost);
- implement previewCitationCluster(), restoreProcessorState(), new "bibchange" flag in processCitationCluster() output, and new opt.sort_citations flag in word processor integration - upgrade to citeproc-js 1.0.29 From Frank's 1.0.25 announcement: Provide new command, previewCitationCluster(), that returns string for hypothetical citation at specified position, without affecting processor state. Fix bug that would have cause appendCitationCluster() to run updateItems() unnecessarily. Provide for forced generation of citationID, for internal use in previewing. From Frank's 1.0.26 announcement: Implement new command restoreProcessorState(), for use in, er, restoring the processor state, when position variables and citation sort keys are already known. From Frank's 1.0.27 announcement: This fixes a couple of obvious problems in the code of the new restoreProcessorState() command. From Frank's 1.0.28 announcement: This version introduces a significant change to the return value of processCitationCluster(). It is now an array with two elements, the first being a JS object that serves as a data segment, and the second the list of two-element arrays representing insertion indexes and strings for insertion (as previously documented). An API change of this scale probably calls for some more visible sign in the version numbering, but the original statement on versioning says that the major and minor numbers will align with the CSL schema, so we stay at level 1.0. The data segment referred to above contains just one element currently, "bibchange", which is true if processing the citation results in any change affecting the bibliography. This release also introduces one change and one addition to style configuration flags. The flag at citation.opt["citation-number-sort"] has been moved to opt.citation_number_sort, for clarity and consistency. A new flag, opt.sort_citations, is true if citations are sorted by the style in any way. From Frank's 1.0.29 announcement: Complete reimplementation of cite-level disambiguation. The new code is more compact and maintainable, and avoids thrashing behavior that afflicted the previous code when a large number of cites required both add-names and year-suffix disambiguation. Suppress year suffix when fresh ambig keys are generated. Inserts by a plugin affecting year suffixes should now be correctly handled.
2010-06-13 22:53:57 +00:00
for each(var newCitation in newCitations[1]) {
this.citationText[citationIndices[newCitation[0]]] = newCitation[1];
this.updateIndices[citationIndices[newCitation[0]]] = true;
}
- implement previewCitationCluster(), restoreProcessorState(), new "bibchange" flag in processCitationCluster() output, and new opt.sort_citations flag in word processor integration - upgrade to citeproc-js 1.0.29 From Frank's 1.0.25 announcement: Provide new command, previewCitationCluster(), that returns string for hypothetical citation at specified position, without affecting processor state. Fix bug that would have cause appendCitationCluster() to run updateItems() unnecessarily. Provide for forced generation of citationID, for internal use in previewing. From Frank's 1.0.26 announcement: Implement new command restoreProcessorState(), for use in, er, restoring the processor state, when position variables and citation sort keys are already known. From Frank's 1.0.27 announcement: This fixes a couple of obvious problems in the code of the new restoreProcessorState() command. From Frank's 1.0.28 announcement: This version introduces a significant change to the return value of processCitationCluster(). It is now an array with two elements, the first being a JS object that serves as a data segment, and the second the list of two-element arrays representing insertion indexes and strings for insertion (as previously documented). An API change of this scale probably calls for some more visible sign in the version numbering, but the original statement on versioning says that the major and minor numbers will align with the CSL schema, so we stay at level 1.0. The data segment referred to above contains just one element currently, "bibchange", which is true if processing the citation results in any change affecting the bibliography. This release also introduces one change and one addition to style configuration flags. The flag at citation.opt["citation-number-sort"] has been moved to opt.citation_number_sort, for clarity and consistency. A new flag, opt.sort_citations, is true if citations are sorted by the style in any way. From Frank's 1.0.29 announcement: Complete reimplementation of cite-level disambiguation. The new code is more compact and maintainable, and avoids thrashing behavior that afflicted the previous code when a large number of cites required both add-names and year-suffix disambiguation. Suppress year suffix when fresh ambig keys are generated. Inserts by a plugin affecting year suffixes should now be correctly handled.
2010-06-13 22:53:57 +00:00
return newCitations.bibchange;
}
}
/**
* Updates the list of citations to be serialized to the document
*/
Zotero.Integration.Session.prototype.updateCitations = function() {
/*var allUpdatesForced = false;
var forcedUpdates = {};
if(force) {
allUpdatesForced = true;
// make sure at least one citation gets updated
updateLoop: for each(var indexList in [this.newIndices, this.updateIndices]) {
for(var i in indexList) {
if(!this.citationsByIndex[i].properties.delete) {
allUpdatesForced = false;
break updateLoop;
}
}
}
if(allUpdatesForced) {
for(i in this.citationsByIndex) {
if(this.citationsByIndex[i] && !this.citationsByIndex[i].properties.delete) {
forcedUpdates[i] = true;
break;
}
}
}
}*/
if(Zotero.Debug.enabled) {
Zotero.debug("Integration: Indices of new citations");
Zotero.debug([key for(key in this.newIndices)]);
Zotero.debug("Integration: Indices of updated citations");
Zotero.debug([key for(key in this.updateIndices)]);
}
var deleteCitations = {};
for each(var indexList in [this.newIndices, this.updateIndices]) {
for(var index in indexList) {
index = parseInt(index);
var citation = this.citationsByIndex[index];
if(citation.properties.delete) {
deleteCitations[index] = true;
continue;
}
if(this.formatCitation(index, citation)) {
this.bibliographyHasChanged = true;
}
if(!this.citationIDs[citation.citationID]) {
this.citationIDs[citation.citationID] = citation;
}
delete this.newIndices[index];
}
}
/*if(allUpdatesForced) {
this.newIndices = {};
this.updateIndices = {};
}*/
return deleteCitations;
}
- implement previewCitationCluster(), restoreProcessorState(), new "bibchange" flag in processCitationCluster() output, and new opt.sort_citations flag in word processor integration - upgrade to citeproc-js 1.0.29 From Frank's 1.0.25 announcement: Provide new command, previewCitationCluster(), that returns string for hypothetical citation at specified position, without affecting processor state. Fix bug that would have cause appendCitationCluster() to run updateItems() unnecessarily. Provide for forced generation of citationID, for internal use in previewing. From Frank's 1.0.26 announcement: Implement new command restoreProcessorState(), for use in, er, restoring the processor state, when position variables and citation sort keys are already known. From Frank's 1.0.27 announcement: This fixes a couple of obvious problems in the code of the new restoreProcessorState() command. From Frank's 1.0.28 announcement: This version introduces a significant change to the return value of processCitationCluster(). It is now an array with two elements, the first being a JS object that serves as a data segment, and the second the list of two-element arrays representing insertion indexes and strings for insertion (as previously documented). An API change of this scale probably calls for some more visible sign in the version numbering, but the original statement on versioning says that the major and minor numbers will align with the CSL schema, so we stay at level 1.0. The data segment referred to above contains just one element currently, "bibchange", which is true if processing the citation results in any change affecting the bibliography. This release also introduces one change and one addition to style configuration flags. The flag at citation.opt["citation-number-sort"] has been moved to opt.citation_number_sort, for clarity and consistency. A new flag, opt.sort_citations, is true if citations are sorted by the style in any way. From Frank's 1.0.29 announcement: Complete reimplementation of cite-level disambiguation. The new code is more compact and maintainable, and avoids thrashing behavior that afflicted the previous code when a large number of cites required both add-names and year-suffix disambiguation. Suppress year suffix when fresh ambig keys are generated. Inserts by a plugin affecting year suffixes should now be correctly handled.
2010-06-13 22:53:57 +00:00
/**
* Restores processor state from document, without requesting citation updates
- implement previewCitationCluster(), restoreProcessorState(), new "bibchange" flag in processCitationCluster() output, and new opt.sort_citations flag in word processor integration - upgrade to citeproc-js 1.0.29 From Frank's 1.0.25 announcement: Provide new command, previewCitationCluster(), that returns string for hypothetical citation at specified position, without affecting processor state. Fix bug that would have cause appendCitationCluster() to run updateItems() unnecessarily. Provide for forced generation of citationID, for internal use in previewing. From Frank's 1.0.26 announcement: Implement new command restoreProcessorState(), for use in, er, restoring the processor state, when position variables and citation sort keys are already known. From Frank's 1.0.27 announcement: This fixes a couple of obvious problems in the code of the new restoreProcessorState() command. From Frank's 1.0.28 announcement: This version introduces a significant change to the return value of processCitationCluster(). It is now an array with two elements, the first being a JS object that serves as a data segment, and the second the list of two-element arrays representing insertion indexes and strings for insertion (as previously documented). An API change of this scale probably calls for some more visible sign in the version numbering, but the original statement on versioning says that the major and minor numbers will align with the CSL schema, so we stay at level 1.0. The data segment referred to above contains just one element currently, "bibchange", which is true if processing the citation results in any change affecting the bibliography. This release also introduces one change and one addition to style configuration flags. The flag at citation.opt["citation-number-sort"] has been moved to opt.citation_number_sort, for clarity and consistency. A new flag, opt.sort_citations, is true if citations are sorted by the style in any way. From Frank's 1.0.29 announcement: Complete reimplementation of cite-level disambiguation. The new code is more compact and maintainable, and avoids thrashing behavior that afflicted the previous code when a large number of cites required both add-names and year-suffix disambiguation. Suppress year suffix when fresh ambig keys are generated. Inserts by a plugin affecting year suffixes should now be correctly handled.
2010-06-13 22:53:57 +00:00
*/
Zotero.Integration.Session.prototype.restoreProcessorState = function() {
var citations = [];
2011-03-03 07:09:08 +00:00
for(var i in this.citationsByIndex) {
- implement previewCitationCluster(), restoreProcessorState(), new "bibchange" flag in processCitationCluster() output, and new opt.sort_citations flag in word processor integration - upgrade to citeproc-js 1.0.29 From Frank's 1.0.25 announcement: Provide new command, previewCitationCluster(), that returns string for hypothetical citation at specified position, without affecting processor state. Fix bug that would have cause appendCitationCluster() to run updateItems() unnecessarily. Provide for forced generation of citationID, for internal use in previewing. From Frank's 1.0.26 announcement: Implement new command restoreProcessorState(), for use in, er, restoring the processor state, when position variables and citation sort keys are already known. From Frank's 1.0.27 announcement: This fixes a couple of obvious problems in the code of the new restoreProcessorState() command. From Frank's 1.0.28 announcement: This version introduces a significant change to the return value of processCitationCluster(). It is now an array with two elements, the first being a JS object that serves as a data segment, and the second the list of two-element arrays representing insertion indexes and strings for insertion (as previously documented). An API change of this scale probably calls for some more visible sign in the version numbering, but the original statement on versioning says that the major and minor numbers will align with the CSL schema, so we stay at level 1.0. The data segment referred to above contains just one element currently, "bibchange", which is true if processing the citation results in any change affecting the bibliography. This release also introduces one change and one addition to style configuration flags. The flag at citation.opt["citation-number-sort"] has been moved to opt.citation_number_sort, for clarity and consistency. A new flag, opt.sort_citations, is true if citations are sorted by the style in any way. From Frank's 1.0.29 announcement: Complete reimplementation of cite-level disambiguation. The new code is more compact and maintainable, and avoids thrashing behavior that afflicted the previous code when a large number of cites required both add-names and year-suffix disambiguation. Suppress year suffix when fresh ambig keys are generated. Inserts by a plugin affecting year suffixes should now be correctly handled.
2010-06-13 22:53:57 +00:00
if(this.citationsByIndex[i] && !this.newIndices[i] && !this.citationsByIndex[i].properties.delete) {
citations.push(this.citationsByIndex[i]);
}
}
this.style.restoreProcessorState(citations);
}
/**
* Loads document data from a JSON object
2007-10-23 07:11:59 +00:00
*/
Zotero.Integration.Session.prototype.loadBibliographyData = function(json) {
var openBraceIndex = json.indexOf("{");
if(openBraceIndex == -1) return;
try {
var documentData = JSON.parse(json.substring(openBraceIndex, json.lastIndexOf("}")+1));
} catch(e) {
try {
var documentData = JSON.parse(json.substr(0, json.length-1));
} catch(e) {
throw new Zotero.Integration.CorruptFieldException(json);
}
}
var needUpdate;
2007-10-23 07:11:59 +00:00
// set uncited
if(documentData.uncited) {
if(documentData.uncited[0]) {
// new style array of arrays with URIs
let zoteroItem, needUpdate;
for each(var uris in documentData.uncited) {
[zoteroItem, needUpdate] = this.uriMap.getZoteroItemForURIs(uris);
if(zoteroItem && !this.citationsByItemID[zoteroItem.id]) {
this.uncitedItems[zoteroItem.id] = true;
} else {
needUpdate = true;
}
this.bibliographyDataHasChanged |= needUpdate;
}
} else {
for(var itemID in documentData.uncited) {
// if not yet in item set, add to item set
var zoteroItem = Zotero.Items.getByKey(itemID);
if(!zoteroItem) zoteroItem = Zotero.Items.get(itemID);
if(zoteroItem) this.uncitedItems[zoteroItem.id] = true;
}
this.bibliographyDataHasChanged = true;
}
this.updateUncitedItems();
2007-10-23 07:11:59 +00:00
}
// set custom bibliography entries
if(documentData.custom) {
if(documentData.custom[0]) {
// new style array of arrays with URIs
var zoteroItem, needUpdate;
for each(var custom in documentData.custom) {
[zoteroItem, needUpdate] = this.uriMap.getZoteroItemForURIs(custom[0]);
if(!zoteroItem) continue;
if(needUpdate) this.bibliographyDataHasChanged = true;
if(this.citationsByItemID[zoteroItem.id] || this.uncitedItems[zoteroItem.id]) {
this.customBibliographyText[zoteroItem.id] = custom[1];
}
}
} else {
// old style hash
for(var itemID in documentData.custom) {
var zoteroItem = Zotero.Items.getByKey(itemID);
if(!zoteroItem) zoteroItem = Zotero.Items.get(itemID);
if(!zoteroItem) continue;
if(this.citationsByItemID[zoteroItem.id] || this.uncitedItems[zoteroItem.id]) {
this.customBibliographyText[zoteroItem.id] = documentData.custom[itemID];
}
}
this.bibliographyDataHasChanged = true;
}
2007-10-23 07:11:59 +00:00
}
// set entries to be omitted from bibliography
if(documentData.omitted) {
let zoteroItem, needUpdate;
for each(var uris in documentData.omitted) {
[zoteroItem, update] = this.uriMap.getZoteroItemForURIs(uris);
if(zoteroItem && this.citationsByItemID[zoteroItem.id]) {
this.omittedItems[zoteroItem.id] = true;
} else {
needUpdate = true;
}
this.bibliographyDataHasChanged |= needUpdate;
}
}
this.bibliographyData = json;
2007-10-23 07:11:59 +00:00
}
/**
* Saves document data from a JSON object
2007-10-23 07:11:59 +00:00
*/
Zotero.Integration.Session.prototype.getBibliographyData = function() {
var bibliographyData = {};
2007-10-23 07:11:59 +00:00
// add uncited if there is anything
for(var item in this.uncitedItems) {
if(item) {
if(!bibliographyData.uncited) bibliographyData.uncited = [];
bibliographyData.uncited.push(this.uriMap.getURIsForItemID(item));
}
2007-10-23 07:11:59 +00:00
}
for(var item in this.omittedItems) {
if(item) {
if(!bibliographyData.omitted) bibliographyData.omitted = [];
bibliographyData.omitted.push(this.uriMap.getURIsForItemID(item));
}
}
2007-10-23 07:11:59 +00:00
// look for custom bibliography entries
bibliographyData.custom = [[this.uriMap.getURIsForItemID(id), this.customBibliographyText[id]]
for(id in this.customBibliographyText)];
if(bibliographyData.uncited || bibliographyData.custom) {
return JSON.stringify(bibliographyData);
2007-10-23 07:11:59 +00:00
} else {
return ""; // nothing
2007-10-23 07:11:59 +00:00
}
}
/**
* Returns a preview, given a citation object (whose citationItems lack item
* and position)
*/
Zotero.Integration.Session.prototype.previewCitation = function(citation) {
- implement previewCitationCluster(), restoreProcessorState(), new "bibchange" flag in processCitationCluster() output, and new opt.sort_citations flag in word processor integration - upgrade to citeproc-js 1.0.29 From Frank's 1.0.25 announcement: Provide new command, previewCitationCluster(), that returns string for hypothetical citation at specified position, without affecting processor state. Fix bug that would have cause appendCitationCluster() to run updateItems() unnecessarily. Provide for forced generation of citationID, for internal use in previewing. From Frank's 1.0.26 announcement: Implement new command restoreProcessorState(), for use in, er, restoring the processor state, when position variables and citation sort keys are already known. From Frank's 1.0.27 announcement: This fixes a couple of obvious problems in the code of the new restoreProcessorState() command. From Frank's 1.0.28 announcement: This version introduces a significant change to the return value of processCitationCluster(). It is now an array with two elements, the first being a JS object that serves as a data segment, and the second the list of two-element arrays representing insertion indexes and strings for insertion (as previously documented). An API change of this scale probably calls for some more visible sign in the version numbering, but the original statement on versioning says that the major and minor numbers will align with the CSL schema, so we stay at level 1.0. The data segment referred to above contains just one element currently, "bibchange", which is true if processing the citation results in any change affecting the bibliography. This release also introduces one change and one addition to style configuration flags. The flag at citation.opt["citation-number-sort"] has been moved to opt.citation_number_sort, for clarity and consistency. A new flag, opt.sort_citations, is true if citations are sorted by the style in any way. From Frank's 1.0.29 announcement: Complete reimplementation of cite-level disambiguation. The new code is more compact and maintainable, and avoids thrashing behavior that afflicted the previous code when a large number of cites required both add-names and year-suffix disambiguation. Suppress year suffix when fresh ambig keys are generated. Inserts by a plugin affecting year suffixes should now be correctly handled.
2010-06-13 22:53:57 +00:00
var citationsPre, citationsPost, citationIndices;
[citationsPre, citationsPost, citationIndices] = this._getPrePost(citation.properties.zoteroIndex);
try {
- implement previewCitationCluster(), restoreProcessorState(), new "bibchange" flag in processCitationCluster() output, and new opt.sort_citations flag in word processor integration - upgrade to citeproc-js 1.0.29 From Frank's 1.0.25 announcement: Provide new command, previewCitationCluster(), that returns string for hypothetical citation at specified position, without affecting processor state. Fix bug that would have cause appendCitationCluster() to run updateItems() unnecessarily. Provide for forced generation of citationID, for internal use in previewing. From Frank's 1.0.26 announcement: Implement new command restoreProcessorState(), for use in, er, restoring the processor state, when position variables and citation sort keys are already known. From Frank's 1.0.27 announcement: This fixes a couple of obvious problems in the code of the new restoreProcessorState() command. From Frank's 1.0.28 announcement: This version introduces a significant change to the return value of processCitationCluster(). It is now an array with two elements, the first being a JS object that serves as a data segment, and the second the list of two-element arrays representing insertion indexes and strings for insertion (as previously documented). An API change of this scale probably calls for some more visible sign in the version numbering, but the original statement on versioning says that the major and minor numbers will align with the CSL schema, so we stay at level 1.0. The data segment referred to above contains just one element currently, "bibchange", which is true if processing the citation results in any change affecting the bibliography. This release also introduces one change and one addition to style configuration flags. The flag at citation.opt["citation-number-sort"] has been moved to opt.citation_number_sort, for clarity and consistency. A new flag, opt.sort_citations, is true if citations are sorted by the style in any way. From Frank's 1.0.29 announcement: Complete reimplementation of cite-level disambiguation. The new code is more compact and maintainable, and avoids thrashing behavior that afflicted the previous code when a large number of cites required both add-names and year-suffix disambiguation. Suppress year suffix when fresh ambig keys are generated. Inserts by a plugin affecting year suffixes should now be correctly handled.
2010-06-13 22:53:57 +00:00
return this.style.previewCitationCluster(citation, citationsPre, citationsPost, "rtf");
} catch(e) {
Zotero.debug(e);
throw e;
}
}
/**
* Brings up the addCitationDialog, prepopulated if a citation is provided
*/
Zotero.Integration.Session.prototype.editCitation = function(index, noteIndex, citation) {
var me = this;
var io = new function() { this.wrappedJSObject = this; }
// if there's already a citation, make sure we have item IDs in addition to keys
if(citation) {
var zoteroItem;
for each(var citationItem in citation.citationItems) {
var item = false;
if(!citationItem.id) {
zoteroItem = false;
if(citationItem.uris) {
[zoteroItem, ] = this.uriMap.getZoteroItemForURIs(citationItem.uris);
} else if(citationItem.key) {
zoteroItem = Zotero.Items.getByKey(citationItem.key);
}
if(zoteroItem) citationItem.id = zoteroItem.id;
}
}
}
// create object to hold citation
io.citation = (citation ? JSON.parse(JSON.stringify(citation)) : {"citationItems":{}, "properties":{}});
delete io.citation.properties["formattedCitation"];
delete io.citation.properties["plainCitation"];
delete io.citation.properties["dontUpdate"];
io.citation.properties.zoteroIndex = parseInt(index, 10);
io.citation.properties.noteIndex = parseInt(noteIndex, 10);
// assign preview function
io.previewFunction = function() {
return me.previewCitation(io.citation);
}
// determine whether citation is sortable in current style
- implement previewCitationCluster(), restoreProcessorState(), new "bibchange" flag in processCitationCluster() output, and new opt.sort_citations flag in word processor integration - upgrade to citeproc-js 1.0.29 From Frank's 1.0.25 announcement: Provide new command, previewCitationCluster(), that returns string for hypothetical citation at specified position, without affecting processor state. Fix bug that would have cause appendCitationCluster() to run updateItems() unnecessarily. Provide for forced generation of citationID, for internal use in previewing. From Frank's 1.0.26 announcement: Implement new command restoreProcessorState(), for use in, er, restoring the processor state, when position variables and citation sort keys are already known. From Frank's 1.0.27 announcement: This fixes a couple of obvious problems in the code of the new restoreProcessorState() command. From Frank's 1.0.28 announcement: This version introduces a significant change to the return value of processCitationCluster(). It is now an array with two elements, the first being a JS object that serves as a data segment, and the second the list of two-element arrays representing insertion indexes and strings for insertion (as previously documented). An API change of this scale probably calls for some more visible sign in the version numbering, but the original statement on versioning says that the major and minor numbers will align with the CSL schema, so we stay at level 1.0. The data segment referred to above contains just one element currently, "bibchange", which is true if processing the citation results in any change affecting the bibliography. This release also introduces one change and one addition to style configuration flags. The flag at citation.opt["citation-number-sort"] has been moved to opt.citation_number_sort, for clarity and consistency. A new flag, opt.sort_citations, is true if citations are sorted by the style in any way. From Frank's 1.0.29 announcement: Complete reimplementation of cite-level disambiguation. The new code is more compact and maintainable, and avoids thrashing behavior that afflicted the previous code when a large number of cites required both add-names and year-suffix disambiguation. Suppress year suffix when fresh ambig keys are generated. Inserts by a plugin affecting year suffixes should now be correctly handled.
2010-06-13 22:53:57 +00:00
io.sortable = this.style.opt.sort_citations;
// citeproc-js style object for use of third-party extension
io.style = this.style;
if(Zotero.Prefs.get("integration.useClassicAddCitationDialog")) {
this._displayDialog('chrome://zotero/content/integration/addCitationDialog.xul', 'resizable', io);
} else {
this._displayDialog('chrome://zotero/content/integration/quickFormat.xul', '', io);
}
if(io.citation.citationItems.length) { // we have an item
this.addCitation(index, noteIndex, io.citation);
this.updateIndices[index] = true;
}
return !!io.citation.citationItems.length;
}
/**
* Edits integration bibliography
*/
Zotero.Integration.Session.prototype.editBibliography = function() {
var bibliographyEditor = new Zotero.Integration.Session.BibliographyEditInterface(this);
var io = new function() { this.wrappedJSObject = bibliographyEditor; }
this.bibliographyDataHasChanged = this.bibliographyHasChanged = true;
this._displayDialog('chrome://zotero/content/integration/editBibliographyDialog.xul', 'resizable', io);
}
/**
* @class Interface for bibliography editor to alter document bibliography
* @constructor
* Creates a new bibliography editor interface
* @param session {Zotero.Integration.Session}
2007-10-23 07:11:59 +00:00
*/
Zotero.Integration.Session.BibliographyEditInterface = function(session) {
this.session = session;
this._changed = {
"customBibliographyText":{},
"uncitedItems":{},
"omittedItems":{}
}
for(var list in this._changed) {
for(var key in this.session[list]) {
this._changed[list][key] = this.session[list][key];
}
}
this._update();
2007-10-23 07:11:59 +00:00
}
/**
* Updates stored bibliography
*/
Zotero.Integration.Session.BibliographyEditInterface.prototype._update = function() {
this.session.updateUncitedItems();
this.session.style.setOutputFormat("rtf");
this.bibliography = this.session.style.makeBibliography();
Zotero.Cite.removeFromBibliography(this.bibliography, this.session.omittedItems);
for(var i in this.bibliography[0].entry_ids) {
if(this.bibliography[0].entry_ids[i].length != 1) continue;
var itemID = this.bibliography[0].entry_ids[i][0];
if(this.session.customBibliographyText[itemID]) {
this.bibliography[1][i] = this.session.customBibliographyText[itemID];
}
}
2007-10-23 07:11:59 +00:00
}
/**
* Reverts the text of an individual bibliography entry
*/
Zotero.Integration.Session.BibliographyEditInterface.prototype.revert = function(itemID) {
delete this.session.customBibliographyText[itemID];
this._update();
}
/**
* Reverts bibliography to condition in which no edits have been made
*/
Zotero.Integration.Session.BibliographyEditInterface.prototype.revertAll = function() {
for(var list in this._changed) {
this.session[list] = {};
}
this._update();
}
/**
* Reverts bibliography to condition before BibliographyEditInterface was opened
* Does not run _update automatically, since this will usually only happen with a cancel request
*/
Zotero.Integration.Session.BibliographyEditInterface.prototype.cancel = function() {
for(var list in this._changed) {
this.session[list] = this._changed[list];
}
this.session.updateUncitedItems();
}
/**
* Checks whether a given reference is cited within the main document text
*/
2007-10-23 07:11:59 +00:00
Zotero.Integration.Session.BibliographyEditInterface.prototype.isCited = function(item) {
if(this.session.citationsByItemID[item]) return true;
}
/**
* Checks whether an item ID is cited in the bibliography being edited
*/
Zotero.Integration.Session.BibliographyEditInterface.prototype.isEdited = function(itemID) {
if(this.session.customBibliographyText[itemID]) return true;
return false;
}
/**
* Checks whether any citations in the bibliography have been edited
*/
Zotero.Integration.Session.BibliographyEditInterface.prototype.isAnyEdited = function() {
for(var list in this._changed) {
for(var a in this.session[list]) {
return true;
}
}
2007-10-23 07:11:59 +00:00
return false;
}
/**
* Adds an item to the bibliography
*/
Zotero.Integration.Session.BibliographyEditInterface.prototype.add = function(itemID) {
if(this.session.omittedItems[itemID]) {
delete this.session.omittedItems[itemID];
} else {
this.session.uncitedItems[itemID] = true;
}
this._update();
2007-10-23 07:11:59 +00:00
}
/**
* Removes an item from the bibliography being edited
*/
Zotero.Integration.Session.BibliographyEditInterface.prototype.remove = function(itemID) {
if(this.session.uncitedItems[itemID]) {
delete this.session.uncitedItems[itemID];
} else {
this.session.omittedItems[itemID] = true;
}
this._update();
}
/**
* Sets custom bibliography text for a given item
*/
Zotero.Integration.Session.BibliographyEditInterface.prototype.setCustomText = function(itemID, text) {
this.session.customBibliographyText[itemID] = text;
this._update();
}
/**
* A class for parsing and passing around document-specific data
*/
Zotero.Integration.DocumentData = function(string) {
this.style = {};
this.prefs = {};
this.sessionID = null;
if(string) {
this.unserialize(string);
}
}
/**
* Serializes document-specific data as XML
*/
Zotero.Integration.DocumentData.prototype.serializeXML = function() {
var xmlData = <data data-version={DATA_VERSION} zotero-version={Zotero.version}>
<session id={this.sessionID} />
<style id={this.style.styleID} hasBibliography={this.style.hasBibliography ? 1 : 0}
2010-09-27 17:12:41 +00:00
bibliographyStyleHasBeenSet={this.style.bibliographyStyleHasBeenSet ? 1 : 0}/>
<prefs/>
</data>;
for(var pref in this.prefs) {
xmlData.prefs.pref += <pref name={pref} value={this.prefs[pref]}/>
}
XML.prettyPrinting = false;
var output = xmlData.toXMLString().replace("\n", "", "g");
XML.prettyPrinting = true;
return output;
}
/**
* Unserializes document-specific XML
*/
Zotero.Integration.DocumentData.prototype.unserializeXML = function(xmlData) {
if(typeof xmlData == "string") {
var xmlData = new XML(xmlData);
}
this.sessionID = xmlData.session.@id.toString();
this.style = {"styleID":xmlData.style.@id.toString(),
"hasBibliography":(xmlData.style.@hasBibliography.toString() == 1),
"bibliographyStyleHasBeenSet":(xmlData.style.@bibliographyStyleHasBeenSet.toString() == 1)};
this.prefs = {};
for each(var pref in xmlData.prefs.children()) {
var name = pref.@name.toString();
var value = pref.@value.toString();
if(value === "true") {
value = true;
} else if(value === "false") {
value = false;
}
this.prefs[name] = value;
}
if(this.prefs["storeReferences"] === undefined) this.prefs["storeReferences"] = false;
this.zoteroVersion = xmlData["@zotero-version"].length() ? xmlData["@zotero-version"].toString() : "2.0";
this.dataVersion = xmlData["@data-version"].length() ? xmlData["@data-version"].toString() : 2;
}
/**
* Unserializes document-specific data, either as XML or as the string form used previously
*/
Zotero.Integration.DocumentData.prototype.unserialize = function(input) {
if(input[0] == "<") {
this.unserializeXML(input);
} else {
const splitRe = /(^|[^:]):(?!:)/;
var splitOutput = input.split(splitRe);
var prefParameters = [];
for(var i=0; i<splitOutput.length; i+=2) {
prefParameters.push((splitOutput[i]+(splitOutput[i+1] ? splitOutput[i+1] : "")).replace("::", ":", "g"));
}
this.sessionID = prefParameters[0];
this.style = {"styleID":prefParameters[1],
"hasBibliography":(prefParameters[3] == "1" || prefParameters[3] == "True"),
"bibliographyStyleHasBeenSet":false};
this.prefs = {"fieldType":((prefParameters[5] == "1" || prefParameters[5] == "True") ? "Bookmark" : "Field"),
"storeReferences":false};
if(prefParameters[2] == "note") {
if(prefParameters[4] == "1" || prefParameters[4] == "True") {
this.prefs.noteType = Components.interfaces.zoteroIntegrationDocument.NOTE_ENDNOTE;
} else {
this.prefs.noteType = Components.interfaces.zoteroIntegrationDocument.NOTE_FOOTNOTE;
}
} else {
this.prefs.noteType = 0;
}
this.zoteroVersion = "2.0b6 or earlier";
this.dataVersion = 1;
}
}
/**
* Handles mapping of item IDs to URIs
*/
Zotero.Integration.URIMap = function(session) {
this.itemIDURIs = {};
this.session = session;
}
/**
* Adds a given mapping to the URI map
*/
Zotero.Integration.URIMap.prototype.add = function(id, uris) {
this.itemIDURIs[id] = uris;
}
/**
* Gets URIs for a given item ID, and adds to map
*/
Zotero.Integration.URIMap.prototype.getURIsForItemID = function(id) {
if(!this.itemIDURIs[id]) {
this.itemIDURIs[id] = [Zotero.URI.getItemURI(Zotero.Items.get(id))];
}
// Make sure that group relations are included
var uris = this.itemIDURIs[id];
for(var i=0; i<uris.length; i++) {
var relations = Zotero.Relations.getByURIs(uris[i], Zotero.Item.linkedItemPredicate);
for(var j=0, n=relations.length; j<n; j++) {
var newUri = relations[j].object;
if(uris.indexOf(newUri) === -1) uris.push(newUri);
}
}
return this.itemIDURIs[id];
}
/**
* Gets Zotero item for a given set of URIs
*/
Zotero.Integration.URIMap.prototype.getZoteroItemForURIs = function(uris) {
var zoteroItem = false;
var needUpdate = false;
for(var i=0, n=uris.length; i<n; i++) {
// Try getting URI directly
var uri = uris[i];
try {
zoteroItem = Zotero.URI.getURIItem(uri);
Duplicate detection: - Adds a per-library "Duplicate Items" virtual search to the source list -- shows up by default for "My Library" but can be added to and removed from all libraries - Current matching algorithm is very basic: finds exact title matches (after normalizing case/diacritics/punctuation/spacing) and DOI/ISBN matches (untested) - In duplicates view, sets are selected automatically; in other views, duplicate items can be selected manually and the merge interface can be brought up with "Merge Items" in the context menu - Can select a master item and individual fields to merge from other versions - Word processor integration code will automatically find mapped replacements and update documents with new item keys Possible future improvements: - Improved detection algorithms - UI tweaks - Currently if any items differ, all available versions will be shown as master item options, even if only one item is different; probably the earliest equivalent item should be shown for each distinct version - Caching of results for performance - Confidence scale - Creator version selection (currently the creators from the chosen master item are kept) - Merging of matching child items - Better sorting of duplicates if not clustered together by the selected sort column - Relation path compression when merging items that are already mapped to previously removed duplicates Other changes in this commit: - Don't show Trash in word processor integration windows - Consider items in trash to be missing in word processor documents - Selection of special views (Trash, Unfiled, Duplicates) is now restored properly in new windows - Disabled field transform context menu when item isn't editable - Left/right arrow now expands/collapses all selected items instead of just the last-selected row - Relation deletions are now synced - The same items row is now reselected after item deletion - (dev) Zotero.Item.getNotes(), Zotero.Item.getAttachments(), and Zotero.Item.getTags() now return empty arrays rather than FALSE if no matches -- tests on those return values in third-party code will need to be changed - (dev) New function Zotero.Utilities.removeDiacritics(str, lowercaseOnly) -- could be used to generate ASCII BibTeX keys - (dev) New 'tempTable' search condition can take a table to join against -- useful for implementing virtual source lists - (dev) Significant UI code cleanup - (dev) Moved all item pane content into itemPane.xul - Probably various other things Needless to say, this needs testing.
2011-07-22 21:24:38 +00:00
if(zoteroItem) {
// Ignore items in the trash
if(zoteroItem.deleted) {
zoteroItem = false;
} else {
break;
Duplicate detection: - Adds a per-library "Duplicate Items" virtual search to the source list -- shows up by default for "My Library" but can be added to and removed from all libraries - Current matching algorithm is very basic: finds exact title matches (after normalizing case/diacritics/punctuation/spacing) and DOI/ISBN matches (untested) - In duplicates view, sets are selected automatically; in other views, duplicate items can be selected manually and the merge interface can be brought up with "Merge Items" in the context menu - Can select a master item and individual fields to merge from other versions - Word processor integration code will automatically find mapped replacements and update documents with new item keys Possible future improvements: - Improved detection algorithms - UI tweaks - Currently if any items differ, all available versions will be shown as master item options, even if only one item is different; probably the earliest equivalent item should be shown for each distinct version - Caching of results for performance - Confidence scale - Creator version selection (currently the creators from the chosen master item are kept) - Merging of matching child items - Better sorting of duplicates if not clustered together by the selected sort column - Relation path compression when merging items that are already mapped to previously removed duplicates Other changes in this commit: - Don't show Trash in word processor integration windows - Consider items in trash to be missing in word processor documents - Selection of special views (Trash, Unfiled, Duplicates) is now restored properly in new windows - Disabled field transform context menu when item isn't editable - Left/right arrow now expands/collapses all selected items instead of just the last-selected row - Relation deletions are now synced - The same items row is now reselected after item deletion - (dev) Zotero.Item.getNotes(), Zotero.Item.getAttachments(), and Zotero.Item.getTags() now return empty arrays rather than FALSE if no matches -- tests on those return values in third-party code will need to be changed - (dev) New function Zotero.Utilities.removeDiacritics(str, lowercaseOnly) -- could be used to generate ASCII BibTeX keys - (dev) New 'tempTable' search condition can take a table to join against -- useful for implementing virtual source lists - (dev) Significant UI code cleanup - (dev) Moved all item pane content into itemPane.xul - Probably various other things Needless to say, this needs testing.
2011-07-22 21:24:38 +00:00
}
}
} catch(e) {}
// Try merged item mappings
var seen = [];
// Follow merged item relations until we find an item or hit a dead end
while (!zoteroItem) {
var relations = Zotero.Relations.getByURIs(uri, Zotero.Relations.deletedItemPredicate);
// No merged items found
if(!relations.length) {
break;
}
uri = relations[0].object;
// Keep track of mapped URIs in case there's a circular relation
if(seen.indexOf(uri) != -1) {
var msg = "Circular relation for '" + uri + "' in merged item mapping resolution";
Zotero.debug(msg, 2);
Components.utils.reportError(msg);
break;
}
seen.push(uri);
try {
zoteroItem = Zotero.URI.getURIItem(uri);
if(zoteroItem) {
// Ignore items in the trash
if(zoteroItem.deleted) {
zoteroItem = false;
} else {
break;
}
}
} catch(e) {}
}
if(zoteroItem) break;
}
if(zoteroItem) {
// make sure URI is up to date (in case user just began synching)
var newURI = Zotero.URI.getItemURI(zoteroItem);
if(newURI != uris[i]) {
uris[i] = newURI;
needUpdate = true;
}
// cache uris
this.itemIDURIs[zoteroItem.id] = uris;
}
return [zoteroItem, needUpdate];
}