"use strict";
/*
***** BEGIN LICENSE BLOCK *****
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
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Zotero is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with Zotero. If not, see .
***** END LICENSE BLOCK *****
*/
const RESELECT_KEY_URI = 1;
const RESELECT_KEY_ITEM_KEY = 2;
const RESELECT_KEY_ITEM_ID = 3;
const DATA_VERSION = 3;
// Specifies that citations should only be updated if changed
const FORCE_CITATIONS_FALSE = 0;
// Specifies that citations should only be updated if formattedText has changed from what is encoded
// in the field code
const FORCE_CITATIONS_REGENERATE = 1;
// Specifies that citations should be reset regardless of whether formattedText has changed
const FORCE_CITATIONS_RESET_TEXT = 2;
// this is used only for update checking
const INTEGRATION_PLUGINS = ["zoteroMacWordIntegration@zotero.org",
"zoteroOpenOfficeIntegration@zotero.org", "zoteroWinWordIntegration@zotero.org"];
Zotero.Integration = new function() {
Components.utils.import("resource://gre/modules/Services.jsm");
Components.utils.import("resource://gre/modules/AddonManager.jsm");
const INTEGRATION_MIN_VERSIONS = ["3.1.7.SOURCE", "3.5b2.SOURCE", "3.1.3.SOURCE"];
var _tmpFile = null;
var _osascriptFile;
// these need to be global because of GC
var _updateTimer;
// For Carbon and X11
var _carbon, ProcessSerialNumber, SetFrontProcessWithOptions;
var _x11, _x11Display, _x11RootWindow, XClientMessageEvent, XFetchName, XFree, XQueryTree,
XOpenDisplay, XCloseDisplay, XFlush, XDefaultRootWindow, XInternAtom, XSendEvent,
XMapRaised, XGetWindowProperty, X11Atom, X11Bool, X11Display, X11Window, X11Status;
this.currentWindow = false;
this.sessions = {};
/**
* 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
var pipe = null;
var sharedDir = Components.classes["@mozilla.org/file/local;1"].
createInstance(Components.interfaces.nsILocalFile);
sharedDir.initWithPath("/Users/Shared");
if(sharedDir.exists() && sharedDir.isDirectory()) {
var logname = Components.classes["@mozilla.org/process/environment;1"].
getService(Components.interfaces.nsIEnvironment).
get("LOGNAME");
var sharedPipe = sharedDir.clone();
sharedPipe.append(".zoteroIntegrationPipe_"+logname);
if(sharedPipe.exists()) {
if(this.deletePipe(sharedPipe) && sharedDir.isWritable()) {
pipe = sharedPipe;
}
} else if(sharedDir.isWritable()) {
pipe = sharedPipe;
}
}
if(!pipe) {
// on other platforms, or as a fallback, use home directory
pipe = Components.classes["@mozilla.org/file/directory_service;1"].
getService(Components.interfaces.nsIProperties).
get("Home", Components.interfaces.nsIFile);
pipe.append(".zoteroIntegrationPipe");
// destroy old pipe, if one exists
if(!this.deletePipe(pipe)) return;
}
// try to initialize pipe
try {
this.initPipe(pipe);
} catch(e) {
Zotero.logError(e);
}
Zotero.Promise.delay(1000).then(_checkPluginVersions);
}
/**
* Begin listening for integration commands on the given pipe
* @param {String} pipe The path to the pipe
*/
this.initPipe = function(pipe) {
Zotero.IPC.Pipe.initPipeListener(pipe, function(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);
}
}
});
}
/**
* Deletes a defunct pipe on OS X
*/
this.deletePipe = function(pipe) {
try {
if(pipe.exists()) {
Zotero.IPC.safePipeWrite(pipe, "Zotero shutdown\n");
pipe.remove(false);
}
return true;
} catch (e) {
// if pipe can't be deleted, log an error
Zotero.debug("Error removing old integration pipe "+pipe.path, 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 false;
let escapedFifoFile = pipe.path.replace("'", "'\\''");
_executeAppleScript("do shell script \"rmdir '"+escapedFifoFile+"'; rm -f '"+escapedFifoFile+"'\" with administrator privileges", true);
if(pipe.exists()) return false;
} catch(e) {
Zotero.logError(e);
return false;
}
}
}
/**
* Checks to see that plugin versions are up to date.
* @return {Promise} Promise that is resolved with true if versions are up to date
* or with false if they are not.
*/
var _checkPluginVersions = new function () {
var integrationVersionsOK;
return function _checkPluginVersions() {
if(integrationVersionsOK) {
if(integrationVersionsOK === true) {
return Zotero.Promise.resolve(integrationVersionsOK);
} else {
return Zotero.Promise.reject(integrationVersionsOK);
}
}
var deferred = Zotero.Promise.defer();
AddonManager.getAddonsByIDs(INTEGRATION_PLUGINS, function(addons) {
for(var i in addons) {
var addon = addons[i];
if(!addon || addon.userDisabled) continue;
if(Services.vc.compare(INTEGRATION_MIN_VERSIONS[i], addon.version) > 0) {
deferred.reject(integrationVersionsOK = new Zotero.Exception.Alert(
"integration.error.incompatibleVersion2",
[Zotero.version, addon.name, INTEGRATION_MIN_VERSIONS[i]],
"integration.error.title"));
}
}
deferred.resolve(integrationVersionsOK = true);
});
return deferred.promise;
};
}
/**
* Executes an integration command, first checking to make sure that versions are compatible
*/
this.execCommand = new function() {
var inProgress;
return function execCommand(agent, command, docId) {
var document;
if(inProgress) {
Zotero.Integration.activate();
if(Zotero.Integration.currentWindow && !Zotero.Integration.currentWindow.closed) {
Zotero.Integration.currentWindow.focus();
}
Zotero.debug("Integration: Request already in progress; not executing "+agent+" "+command);
return;
}
inProgress = true;
// Check integration component versions
_checkPluginVersions().then(function() {
// Try to load the appropriate Zotero component; otherwise display an error
try {
var componentClass = "@zotero.org/Zotero/integration/application?agent="+agent+";1";
Zotero.debug("Integration: Instantiating "+componentClass+" for command "+command+(docId ? " with doc "+docId : ""));
var application = Components.classes[componentClass]
.getService(Components.interfaces.zoteroIntegrationApplication);
} catch(e) {
throw new Zotero.Exception.Alert("integration.error.notInstalled",
[], "integration.error.title");
}
// Try to execute the command; otherwise display an error in alert service or word processor
// (depending on what is possible)
document = (application.getDocument && docId ? application.getDocument(docId) : application.getActiveDocument());
return Zotero.Promise.resolve((new Zotero.Integration.Document(application, document))[command]());
}).catch(function(e) {
if(!(e instanceof Zotero.Exception.UserCancelled)) {
try {
var displayError = null;
if(e instanceof Zotero.Exception.Alert) {
displayError = e.message;
} else {
if(e.toString().indexOf("ExceptionAlreadyDisplayed") === -1) {
displayError = Zotero.getString("integration.error.generic")+"\n\n"+(e.message || e.toString());
}
if(e.stack) {
Zotero.debug(e.stack);
}
}
if(displayError) {
var showErrorInFirefox = !document;
if(document) {
try {
document.activate();
document.displayAlert(displayError,
Components.interfaces.zoteroIntegrationDocument.DIALOG_ICON_STOP,
Components.interfaces.zoteroIntegrationDocument.DIALOG_BUTTONS_OK);
} catch(e) {
showErrorInFirefox = true;
}
}
if(showErrorInFirefox) {
Zotero.Integration.activate();
Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
.getService(Components.interfaces.nsIPromptService)
.alert(null, Zotero.getString("integration.error.title"), displayError);
}
}
} finally {
Zotero.logError(e);
}
}
})
.finally(function() {
if(document) {
try {
document.cleanup();
document.activate();
// Call complete function if one exists
if(document.wrappedJSObject && document.wrappedJSObject.complete) {
document.wrappedJSObject.complete();
}
} catch(e) {
Zotero.logError(e);
}
}
if(Zotero.Integration.currentWindow && !Zotero.Integration.currentWindow.closed) {
var oldWindow = Zotero.Integration.currentWindow;
Zotero.Promise.delay(100).then(function() {
oldWindow.close();
});
}
inProgress = Zotero.Integration.currentWindow = false;
});
};
};
/**
* Activates Firefox
*/
this.activate = function(win) {
if(Zotero.isMac) {
const BUNDLE_IDS = {
"Zotero":"org.zotero.zotero",
"Firefox":"org.mozilla.firefox",
"Aurora":"org.mozilla.aurora",
"Nightly":"org.mozilla.nightly"
};
if(win) {
Components.utils.import("resource://gre/modules/ctypes.jsm");
win.focus();
if(!_carbon) {
_carbon = ctypes.open("/System/Library/Frameworks/Carbon.framework/Carbon");
/*
* struct ProcessSerialNumber {
* unsigned long highLongOfPSN;
* unsigned long lowLongOfPSN;
* };
*/
ProcessSerialNumber = new ctypes.StructType("ProcessSerialNumber",
[{"highLongOfPSN":ctypes.uint32_t}, {"lowLongOfPSN":ctypes.uint32_t}]);
/*
* OSStatus SetFrontProcessWithOptions (
* const ProcessSerialNumber *inProcess,
* OptionBits inOptions
* );
*/
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)
);
}, false);
} else {
_executeAppleScript('tell application id "'+BUNDLE_IDS[Zotero.appName]+'" to activate');
}
} else if(!Zotero.isWin && win) {
Components.utils.import("resource://gre/modules/ctypes.jsm");
if(_x11 === false) return;
if(!_x11) {
try {
_x11 = ctypes.open("libX11.so.6");
} catch(e) {
try {
var libName = ctypes.libraryName("X11");
} catch(e) {
_x11 = false;
Zotero.debug("Integration: Could not get libX11 name; not activating");
Zotero.logError(e);
return;
}
try {
_x11 = ctypes.open(libName);
} catch(e) {
_x11 = false;
Zotero.debug("Integration: Could not open "+libName+"; not activating");
Zotero.logError(e);
return;
}
}
X11Atom = ctypes.unsigned_long;
X11Bool = ctypes.int;
X11Display = new ctypes.StructType("Display");
X11Window = ctypes.unsigned_long;
X11Status = ctypes.int;
/*
* typedef struct {
* int type;
* unsigned long serial; / * # of last request processed by server * /
* Bool send_event; / * true if this came from a SendEvent request * /
* Display *display; / * Display the event was read from * /
* Window window;
* Atom message_type;
* int format;
* union {
* char b[20];
* short s[10];
* long l[5];
* } data;
* } XClientMessageEvent;
*/
XClientMessageEvent = new ctypes.StructType("XClientMessageEvent",
[
{"type":ctypes.int},
{"serial":ctypes.unsigned_long},
{"send_event":X11Bool},
{"display":X11Display.ptr},
{"window":X11Window},
{"message_type":X11Atom},
{"format":ctypes.int},
{"l0":ctypes.long},
{"l1":ctypes.long},
{"l2":ctypes.long},
{"l3":ctypes.long},
{"l4":ctypes.long}
]
);
/*
* Status XFetchName(
* Display* display,
* Window w,
* char** window_name_return
* );
*/
XFetchName = _x11.declare("XFetchName", ctypes.default_abi, X11Status,
X11Display.ptr, X11Window, ctypes.char.ptr.ptr);
/*
* Status XQueryTree(
* Display* display,
* Window w,
* Window* root_return,
* Window* parent_return,
* Window** children_return,
* unsigned int* nchildren_return
* );
*/
XQueryTree = _x11.declare("XQueryTree", ctypes.default_abi, X11Status,
X11Display.ptr, X11Window, X11Window.ptr, X11Window.ptr, X11Window.ptr.ptr,
ctypes.unsigned_int.ptr);
/*
* int XFree(
* void* data
* );
*/
XFree = _x11.declare("XFree", ctypes.default_abi, ctypes.int, ctypes.voidptr_t);
/*
* Display *XOpenDisplay(
* _Xconst char* display_name
* );
*/
XOpenDisplay = _x11.declare("XOpenDisplay", ctypes.default_abi, X11Display.ptr,
ctypes.char.ptr);
/*
* int XCloseDisplay(
* Display* display
* );
*/
XCloseDisplay = _x11.declare("XCloseDisplay", ctypes.default_abi, ctypes.int,
X11Display.ptr);
/*
* int XFlush(
* Display* display
* );
*/
XFlush = _x11.declare("XFlush", ctypes.default_abi, ctypes.int, X11Display.ptr);
/*
* Window XDefaultRootWindow(
* Display* display
* );
*/
XDefaultRootWindow = _x11.declare("XDefaultRootWindow", ctypes.default_abi,
X11Window, X11Display.ptr);
/*
* Atom XInternAtom(
* Display* display,
* _Xconst char* atom_name,
* Bool only_if_exists
* );
*/
XInternAtom = _x11.declare("XInternAtom", ctypes.default_abi, X11Atom,
X11Display.ptr, ctypes.char.ptr, X11Bool);
/*
* Status XSendEvent(
* Display* display,
* Window w,
* Bool propagate,
* long event_mask,
* XEvent* event_send
* );
*/
XSendEvent = _x11.declare("XSendEvent", ctypes.default_abi, X11Status,
X11Display.ptr, X11Window, X11Bool, ctypes.long, XClientMessageEvent.ptr);
/*
* int XMapRaised(
* Display* display,
* Window w
* );
*/
XMapRaised = _x11.declare("XMapRaised", ctypes.default_abi, ctypes.int,
X11Display.ptr, X11Window);
/*
* extern int XGetWindowProperty(
* Display* display,
* Window w,
* Atom property,
* long long_offset,
* long long_length,
* Bool delete,
* Atom req_type,
* Atom* actual_type_return,
* int* actual_format_return,
* unsigned long* nitems_return,
* unsigned long* bytes_after_return,
* unsigned char** prop_return
* );
*/
XGetWindowProperty = _x11.declare("XGetWindowProperty", ctypes.default_abi,
ctypes.int, X11Display.ptr, X11Window, X11Atom, ctypes.long, ctypes.long,
X11Bool, X11Atom, X11Atom.ptr, ctypes.int.ptr, ctypes.unsigned_long.ptr,
ctypes.unsigned_long.ptr, ctypes.char.ptr.ptr);
_x11Display = XOpenDisplay(null);
if(!_x11Display) {
Zotero.debug("Integration: Could not open display; not activating");
_x11 = false;
return;
}
Zotero.addShutdownListener(function() {
XCloseDisplay(_x11Display);
});
_x11RootWindow = XDefaultRootWindow(_x11Display);
if(!_x11RootWindow) {
Zotero.debug("Integration: Could not get root window; not activating");
_x11 = false;
return;
}
}
win.addEventListener("load", function() {
var intervalID;
intervalID = win.setInterval(function() {
_X11BringToForeground(win, intervalID);
}, 50);
}, false);
}
}
/**
* Get a property from an X11 window
*/
function _X11GetProperty(win, propertyName, propertyType) {
Components.utils.import("resource://gre/modules/ctypes.jsm");
var returnType = new X11Atom(),
returnFormat = new ctypes.int(),
nItemsReturned = new ctypes.unsigned_long(),
nBytesAfterReturn = new ctypes.unsigned_long(),
data = new ctypes.char.ptr();
if(!XGetWindowProperty(_x11Display, win, XInternAtom(_x11Display, propertyName, 0), 0, 1024,
0, propertyType, returnType.address(), returnFormat.address(),
nItemsReturned.address(), nBytesAfterReturn.address(), data.address())) {
var nElements = ctypes.cast(nItemsReturned, ctypes.unsigned_int).value;
if(nElements) return [data, nElements];
}
return null;
}
/**
* Bring a window to the foreground by interfacing directly with X11
*/
function _X11BringToForeground(win, intervalID) {
var windowTitle = win.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation).QueryInterface(Ci.nsIBaseWindow).title;
var x11Window = _X11FindWindow(_x11RootWindow, windowTitle);
if(!x11Window) return;
win.clearInterval(intervalID);
var event = new XClientMessageEvent();
event.type = 33; /* ClientMessage*/
event.serial = 0;
event.send_event = 1;
event.message_type = XInternAtom(_x11Display, "_NET_ACTIVE_WINDOW", 0);
event.display = _x11Display;
event.window = x11Window;
event.format = 32;
event.l0 = 2;
var mask = 1<<20 /* SubstructureRedirectMask */ | 1<<19 /* SubstructureNotifyMask */;
if(XSendEvent(_x11Display, _x11RootWindow, 0, mask, event.address())) {
XMapRaised(_x11Display, x11Window);
XFlush(_x11Display);
Zotero.debug("Integration: Activated successfully");
} else {
Zotero.debug("Integration: An error occurred activating the window");
}
}
/**
* Find an X11 window given a name
*/
function _X11FindWindow(w, searchName) {
Components.utils.import("resource://gre/modules/ctypes.jsm");
var res = _X11GetProperty(w, "_NET_CLIENT_LIST", 33 /** XA_WINDOW **/)
|| _X11GetProperty(w, "_WIN_CLIENT_LIST", 6 /** XA_CARDINAL **/);
if(!res) return false;
var nClients = res[1],
clientList = ctypes.cast(res[0], X11Window.array(nClients).ptr).contents,
foundName = new ctypes.char.ptr();
for(var i=0; i DATA_VERSION) {
return Zotero.Promise.reject(new Zotero.Exception.Alert("integration.error.newerDocumentVersion",
[data.zoteroVersion, Zotero.version], "integration.error.title"));
}
if(data.prefs.fieldType !== this._app.primaryFieldType
&& data.prefs.fieldType !== this._app.secondaryFieldType) {
return Zotero.Promise.reject(new Zotero.Exception.Alert("integration.error.fieldTypeMismatch",
[], "integration.error.title"));
}
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.Exception.Alert && e.name === "integration.error.invalidStyle") {
return this._session.setDocPrefs(this._doc, this._app.primaryFieldType,
this._app.secondaryFieldType).then(function(status) {
me._doc.setDocumentData(me._session.data.serializeXML());
me._session.reload = true;
return me._session;
});
} else {
return Zotero.Promise.reject(e);
}
}
this._doc.setDocumentData(this._session.data.serializeXML());
this._session.reload = true;
}
return Zotero.Promise.resolve(this._session);
}
};
/**
* Adds a citation to the current document.
* @return {Promise}
*/
Zotero.Integration.Document.prototype.addCitation = function() {
var me = this;
return this._getSession(false, false).then(function() {
return (new Zotero.Integration.Fields(me._session, me._doc)).addEditCitation(null);
});
}
/**
* Edits the citation at the cursor position.
* @return {Promise}
*/
Zotero.Integration.Document.prototype.editCitation = function() {
var me = this;
return this._getSession(true, false).then(function() {
var field = me._doc.cursorInField(me._session.data.prefs['fieldType']);
if(!field) {
throw new Zotero.Exception.Alert("integration.error.notInCitation", [],
"integration.error.title");
}
return (new Zotero.Integration.Fields(me._session, me._doc)).addEditCitation(field);
});
}
/**
* Edits the citation at the cursor position if one exists, or else adds a new one.
* @return {Promise}
*/
Zotero.Integration.Document.prototype.addEditCitation = function() {
var me = this;
return this._getSession(false, false).then(function() {
var field = me._doc.cursorInField(me._session.data.prefs['fieldType']);
return (new Zotero.Integration.Fields(me._session, me._doc)).addEditCitation(field);
});
}
/**
* Adds a bibliography to the current document.
* @return {Promise}
*/
Zotero.Integration.Document.prototype.addBibliography = function() {
var me = this;
return this._getSession(true, false).then(function() {
// Make sure we can have a bibliography
if(!me._session.data.style.hasBibliography) {
throw new Zotero.Exception.Alert("integration.error.noBibliography", [],
"integration.error.title");
}
var fieldGetter = new Zotero.Integration.Fields(me._session, me._doc, Zotero.Integration.onFieldError);
return fieldGetter.addField().then(function(field) {
field.setCode("BIBL");
return fieldGetter.updateSession().then(function() {
return fieldGetter.updateDocument(FORCE_CITATIONS_FALSE, true, false);
});
});
});
}
/**
* Edits bibliography metadata.
* @return {Promise}
*/
Zotero.Integration.Document.prototype.editBibliography = function() {
// Make sure we have a bibliography
var me = this, fieldGetter;
return this._getSession(true, false).then(function() {
fieldGetter = new Zotero.Integration.Fields(me._session, me._doc, Zotero.Integration.onFieldError);
return fieldGetter.get();
}).then(function(fields) {
var haveBibliography = false;
for(var i=fields.length-1; i>=0; i--) {
var code = fields[i].getCode();
var [type, content] = fieldGetter.getCodeTypeAndContent(code);
if(type == INTEGRATION_TYPE_BIBLIOGRAPHY) {
haveBibliography = true;
break;
}
}
if(!haveBibliography) {
throw new Zotero.Exception.Alert("integration.error.mustInsertBibliography",
[], "integration.error.title");
}
return fieldGetter.updateSession();
}).then(function() {
return me._session.editBibliography(me._doc);
}).then(function() {
return fieldGetter.updateDocument(FORCE_CITATIONS_FALSE, true, false);
});
}
/**
* Updates the citation data for all citations and bibliography entries.
* @return {Promise}
*/
Zotero.Integration.Document.prototype.refresh = function() {
var me = this;
return this._getSession(true, false).then(function() {
// Send request, forcing update of citations and bibliography
var fieldGetter = new Zotero.Integration.Fields(me._session, me._doc, Zotero.Integration.onFieldError);
return fieldGetter.updateSession().then(function() {
return fieldGetter.updateDocument(FORCE_CITATIONS_REGENERATE, true, false);
});
});
}
/**
* Deletes field codes.
* @return {Promise}
*/
Zotero.Integration.Document.prototype.removeCodes = function() {
var me = this;
return this._getSession(true, false).then(function() {
var fieldGetter = new Zotero.Integration.Fields(me._session, me._doc);
return fieldGetter.get()
}).then(function(fields) {
var result = me._doc.displayAlert(Zotero.getString("integration.removeCodesWarning"),
Components.interfaces.zoteroIntegrationDocument.DIALOG_ICON_WARNING,
Components.interfaces.zoteroIntegrationDocument.DIALOG_BUTTONS_OK_CANCEL);
if(result) {
for(var i=fields.length-1; i>=0; i--) {
fields[i].removeCode();
}
}
});
}
/**
* Displays a dialog to set document preferences (style, footnotes/endnotes, etc.)
* @return {Promise}
*/
Zotero.Integration.Document.prototype.setDocPrefs = function() {
var me = this,
fieldGetter,
oldData;
return this._getSession(false, true).then(function(haveSession) {
fieldGetter = new Zotero.Integration.Fields(me._session, me._doc, Zotero.Integration.onFieldError);
var setDocPrefs = me._session.setDocPrefs.bind(me._session, me._doc,
me._app.primaryFieldType, me._app.secondaryFieldType);
if(!haveSession) {
// This is a brand new document; don't try to get fields
return setDocPrefs();
} else if(me._session.reload) {
// Always reload before setDocPrefs so we can permit/deny unchecking storeReferences as
// appropriate
return fieldGetter.updateSession().then(setDocPrefs);
} else {
// Can get fields while dialog is open
return Zotero.Promise.all([
fieldGetter.get(),
setDocPrefs()
]).spread(function (fields, setDocPrefs) {
// Only return value from setDocPrefs
return setDocPrefs;
});
}
}).then(function(aOldData) { // After setDocPrefs call
oldData = aOldData;
// Write document data to document
me._doc.setDocumentData(me._session.data.serializeXML());
// If oldData is null, then there was no document data, so we don't need to update
// fields
if(!oldData) return false;
return fieldGetter.get();
}).then(function(fields) {
if(!fields || !fields.length) return;
// 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 != me._session.data.prefs.fieldType;
var convertItems = convertBibliographies
|| oldData.prefs.noteType != me._session.data.prefs.noteType;
var fieldsToConvert = new Array();
var fieldNoteTypes = new Array();
for(var i=0, n=fields.length; i {})
.then(function() {
me._session.updateIndices = {};
me._session.updateItemIDs = {};
me._session.citationText = {};
me._session.bibliographyHasChanged = false;
delete me._session.reload;
});
} else {
return;
}
});
}
/**
* Keep processing fields until all have been processed
*/
Zotero.Integration.Fields.prototype._processFields = function(i) {
if(!i) i = 0;
for(var n = this._fields.length; i {}).then(function() {
return Zotero.Promise.each(
me._updateDocument(
forceCitations, forceBibliography, ignoreCitationChanges
),
() => {}
);
});
}
/**
* Helper function to update bibliographys and fields within a document
* @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
*/
Zotero.Integration.Fields.prototype._updateDocument = function* (forceCitations, forceBibliography,
ignoreCitationChanges) {
if(this.progressCallback) {
var nFieldUpdates = Object.keys(this._session.updateIndices).length;
if(this._session.bibliographyHasChanged || forceBibliography) {
nFieldUpdates += this._bibliographyFields.length*5;
}
}
var nUpdated=0;
for(var i in this._session.updateIndices) {
if(this.progressCallback && nUpdated % 10 == 0) {
try {
this.progressCallback(75+(nUpdated/nFieldUpdates)*25);
} catch(e) {
Zotero.logError(e);
}
yield;
}
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 || citation.properties.delete) continue;
var isRich = false;
if(!citation.properties.dontUpdate) {
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 === FORCE_CITATIONS_RESET_TEXT
|| citation.properties.formattedCitation !== formattedCitation) {
// Check if citation has been manually modified
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
Zotero.debug("[_updateDocument] Attempting to update manually modified citation.\n"
+ "Original: " + citation.properties.plainCitation + "\n"
+ "Current: " + plainCitation
);
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
&& !citation.properties.dontUpdate) {
// 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(formattedCitation, isRich);
}
}
nUpdated++;
}
// update bibliographies
if(this._bibliographyFields.length // if bibliography exists
&& (this._session.bibliographyHasChanged // and bibliography changed
|| forceBibliography)) { // or if we should generate regardless of
// changes
var bibliographyFields = this._bibliographyFields;
if(forceBibliography || this._session.bibliographyDataHasChanged) {
var bibliographyData = this._session.getBibliographyData();
for (let field of bibliographyFields) {
field.setCode("BIBL "+bibliographyData
+(this._session.data.prefs.storeReferences ? " CSL_BIBLIOGRAPHY" : ""));
}
}
// get bibliography and format as RTF
var bib = this._session.getBibliography();
var bibliographyText = "";
if(bib) {
bibliographyText = bib[0].bibstart+bib[1].join("\\\r\n")+"\\\r\n"+bib[0].bibend;
// if bibliography style not set, set it
if(!this._session.data.style.bibliographyStyleHasBeenSet) {
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
this._session.data.style.bibliographyStyleHasBeenSet = true;
this._doc.setDocumentData(this._session.data.serializeXML());
}
}
// set bibliography text
for (let field of bibliographyFields) {
if(this.progressCallback) {
try {
this.progressCallback(75+(nUpdated/nFieldUpdates)*25);
} catch(e) {
Zotero.logError(e);
}
yield;
}
if(bibliographyText) {
field.setText(bibliographyText, true);
} else {
field.setText("{Bibliography}", false);
}
nUpdated += 5;
}
}
// Do these operations in reverse in case plug-ins care about order
for(var i=this._session.citationsByIndex.length-1; i>=0; i--) {
if(this._session.citationsByIndex[i] &&
this._session.citationsByIndex[i].properties.delete) {
this._fields[i].delete();
}
}
var removeCodeFields = Object.keys(this._removeCodeFields).sort();
for(var i=(removeCodeFields.length-1); i>=0; i--) {
this._fields[removeCodeFields[i]].removeCode();
}
}
/**
* Brings up the addCitationDialog, prepopulated if a citation is provided
*/
Zotero.Integration.Fields.prototype.addEditCitation = function(field) {
var newField, citation, fieldIndex, session = this._session;
// if there's already a citation, make sure we have item IDs in addition to keys
if(field) {
try {
var code = field.getCode();
} catch(e) {}
if(code) {
var [type, content] = this.getCodeTypeAndContent(code);
if(type != INTEGRATION_TYPE_ITEM) {
throw new Zotero.Exception.Alert("integration.error.notInCitation");
}
try {
citation = session.unserializeCitation(content);
} catch(e) {}
if(citation) {
try {
session.lookupItems(citation);
} catch(e) {
if(e instanceof Zotero.Integration.MissingItemException) {
citation.citationItems = [];
} else {
throw e;
}
}
if(citation.properties.dontUpdate
|| (citation.properties.plainCitation
&& field.getText() !== citation.properties.plainCitation)) {
this._doc.activate();
Zotero.debug("[addEditCitation] Attempting to update manually modified citation.\n"
+ "citation.properties.dontUpdate: " + citation.properties.dontUpdate + "\n"
+ "Original: " + citation.properties.plainCitation + "\n"
+ "Current: " + field.getText()
);
if(!this._doc.displayAlert(Zotero.getString("integration.citationChanged.edit"),
Components.interfaces.zoteroIntegrationDocument.DIALOG_ICON_WARNING,
Components.interfaces.zoteroIntegrationDocument.DIALOG_BUTTONS_OK_CANCEL)) {
throw new Zotero.Exception.UserCancelled("editing citation");
}
}
// make sure it's going to get updated
delete citation.properties["formattedCitation"];
delete citation.properties["plainCitation"];
delete citation.properties["dontUpdate"];
}
}
} else {
newField = true;
field = this.addField(true);
}
var me = this;
return Zotero.Promise.resolve(field).then(function(field) {
if(!citation) {
field.setCode("TEMP");
citation = {"citationItems":[], "properties":{}};
}
var io = new Zotero.Integration.CitationEditInterface(citation, field, me, session);
if(Zotero.Prefs.get("integration.useClassicAddCitationDialog")) {
Zotero.Integration.displayDialog(me._doc,
'chrome://zotero/content/integration/addCitationDialog.xul', 'alwaysRaised,resizable',
io);
} else {
var mode = (!Zotero.isMac && Zotero.Prefs.get('integration.keepAddCitationDialogRaised')
? 'popup' : 'alwaysRaised')+',resizable=false';
Zotero.Integration.displayDialog(me._doc,
'chrome://zotero/content/integration/quickFormat.xul', mode, io);
}
if(newField) {
return io.promise.catch(function(e) {
// Try to delete new field on failure
try {
field.delete();
} catch(e) {}
throw e;
});
} else {
return io.promise;
}
});
}
/**
* Citation editing functions and propertiesaccessible to quickFormat.js and addCitationDialog.js
*/
Zotero.Integration.CitationEditInterface = function(citation, field, fieldGetter, session) {
this.citation = citation;
this._field = field;
this._fieldGetter = fieldGetter;
this._session = session;
this._sessionUpdateResolveErrors = false;
this._sessionUpdateDeferreds = [];
// Needed to make this work across boundaries
this.wrappedJSObject = this;
// Determine whether citation is sortable in current style
this.sortable = session.style.opt.sort_citations;
// Citeproc-js style object for use of third-party extension
this.style = session.style;
// Start getting citation data
this._acceptDeferred = Zotero.Promise.defer();
this._fieldIndexPromise = fieldGetter.get().then(function(fields) {
for(var i=0, n=fields.length; i {
return citationsByItemID[itemID]
&& citationsByItemID[itemID].length
// Exclude the present item
&& (citationsByItemID[itemID].length > 1
|| citationsByItemID[itemID][0].properties.zoteroIndex !== this._fieldIndex);
});
// Sort all previously cited items at top, and all items cited later at bottom
var fieldIndex = this._fieldIndex;
ids.sort(function(a, b) {
var indexA = citationsByItemID[a][0].properties.zoteroIndex,
indexB = citationsByItemID[b][0].properties.zoteroIndex;
if(indexA >= fieldIndex){
if(indexB < fieldIndex) return 1;
return indexA - indexB;
}
if(indexB > fieldIndex) return -1;
return indexB - indexA;
});
return Zotero.Cite.getItem(ids);
}
}
/**
* Keeps track of all session-specific variables
*/
Zotero.Integration.Session = function(doc) {
// holds items not in document that should be in bibliography
this.uncitedItems = {};
this.omittedItems = {};
this.embeddedItems = {};
this.embeddedZoteroItems = {};
this.embeddedZoteroItemsByURI = {};
this.customBibliographyText = {};
this.reselectedItems = {};
this.resetRequest(doc);
}
/**
* Resets per-request variables in the CitationSet
*/
Zotero.Integration.Session.prototype.resetRequest = function(doc) {
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.citeprocCitationIDs;
this.citationsByItemID = {};
this.citationsByIndex = [];
this.documentCitationIDs = {};
this.citeprocCitationIDs = {};
this.citationText = {};
this.doc = doc;
}
/**
* Changes the Session style and data
* @param data {Zotero.Integration.DocumentData}
* @param resetStyle {Boolean} Whether to force the style to be reset
* regardless of whether it has changed. This is desirable if the
* automaticJournalAbbreviations or locale has changed.
*/
Zotero.Integration.Session.prototype.setData = function(data, resetStyle) {
var oldStyle = (this.data && this.data.style ? this.data.style : false);
this.data = data;
if(data.style.styleID && (!oldStyle || oldStyle.styleID != data.style.styleID || resetStyle)) {
this.styleID = data.style.styleID;
try {
var getStyle = Zotero.Styles.get(data.style.styleID);
data.style.hasBibliography = getStyle.hasBibliography;
this.style = getStyle.getCiteProc(data.style.locale, data.prefs.automaticJournalAbbreviations);
this.style.setOutputFormat("rtf");
this.styleClass = getStyle.class;
this.dateModified = new Object();
} catch(e) {
Zotero.logError(e);
data.style.styleID = undefined;
throw new Zotero.Exception.Alert("integration.error.invalidStyle");
}
return true;
} else if(oldStyle) {
data.style = oldStyle;
}
return false;
}
/**
* Displays a dialog to set document preferences
* @return {Promise} A promise resolved with old document data, if there was any or null,
* if there wasn't, or rejected with Zotero.Exception.UserCancelled if the dialog was
* cancelled.
*/
Zotero.Integration.Session.prototype.setDocPrefs = function(doc, primaryFieldType, secondaryFieldType) {
var io = new function() {
this.wrappedJSObject = this;
};
if(this.data) {
io.style = this.data.style.styleID;
io.locale = this.data.style.locale;
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;
io.automaticJournalAbbreviations = this.data.prefs.automaticJournalAbbreviations;
io.requireStoreReferences = !Zotero.Utilities.isEmpty(this.embeddedItems);
}
var me = this;
return Zotero.Integration.displayDialog(doc,
'chrome://zotero/content/integration/integrationDocPrefs.xul', '', io)
.then(function() {
if(!io.style) {
throw new Zotero.Exception.UserCancelled("document preferences window");
}
// set data
var oldData = me.data;
var data = new Zotero.Integration.DocumentData();
data.sessionID = oldData.sessionID;
data.style.styleID = io.style;
data.style.locale = io.locale;
data.prefs.fieldType = io.fieldType;
data.prefs.storeReferences = io.storeReferences;
data.prefs.automaticJournalAbbreviations = io.automaticJournalAbbreviations;
var forceStyleReset = oldData
&& (
oldData.prefs.automaticJournalAbbreviations != data.prefs.automaticJournalAbbreviations
|| oldData.style.locale != io.locale
);
me.setData(data, forceStyleReset);
// need to do this after setting the data so that we know if it's a note style
me.data.prefs.noteType = me.style && me.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
|| oldData.prefs.automaticJournalAbbreviations != data.prefs.automaticJournalAbbreviations) {
// This will cause us to regenerate all citations
me.oldCitationIDs = {};
}
return oldData || null;
});
}
/**
* Reselects an item to replace a deleted item
* @param exception {Zotero.Integration.MissingItemException}
*/
Zotero.Integration.Session.prototype.reselectItem = function(doc, exception) {
var io = new function() { this.wrappedJSObject = this; },
me = this;
io.addBorder = Zotero.isWin;
io.singleSelection = true;
return Zotero.Integration.displayDialog(doc, 'chrome://zotero/content/selectItemsDialog.xul',
'resizable', io).then(function() {
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) {
me.reselectedItems[reselectKey] = itemID;
}
// add old URIs to map, so that they will be included
if(exception.reselectKeyType == RESELECT_KEY_URI) {
me.uriMap.add(itemID, exception.reselectKeys.concat(me.uriMap.getURIsForItemID(itemID)));
}
// flag for update
me.updateItemIDs[itemID] = true;
}
});
}
/**
* Generates a field from a citation object
*/
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 = [];
field.push('"citationID":'+uneval(citation.citationID));
var properties = JSON.stringify(citation.properties, saveProperties);
if(properties != "{}") {
field.push('"properties":'+properties);
}
var m = citation.citationItems.length;
var citationItems = new Array(m);
for(var j=0; j parseInt(i)));
}
/**
* Refreshes updateIndices variable to include fields for modified items
*/
Zotero.Integration.Session.prototype.updateUpdateIndices = function(regenerateAll) {
if(regenerateAll || this.regenerateAll) {
// update all indices
for(var i in this.citationsByIndex) {
this.newIndices[i] = true;
this.updateIndices[i] = true;
}
} 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.uriMap.getURIsForItemID(id), this.customBibliographyText[id]]);
if(bibliographyData.uncited || bibliographyData.custom) {
return JSON.stringify(bibliographyData);
} else {
return ""; // nothing
}
}
/**
* Returns a preview, given a citation object (whose citationItems lack item
* and position)
*/
Zotero.Integration.Session.prototype.previewCitation = function(citation) {
var citationsPre, citationsPost, citationIndices;
[citationsPre, citationsPost, citationIndices] = this._getPrePost(citation.properties.zoteroIndex);
try {
return this.style.previewCitationCluster(citation, citationsPre, citationsPost, "rtf");
} catch(e) {
throw e;
}
}
/**
* Edits integration bibliography
*/
Zotero.Integration.Session.prototype.editBibliography = function(doc) {
var bibliographyEditor = new Zotero.Integration.Session.BibliographyEditInterface(this);
var io = new function() { this.wrappedJSObject = bibliographyEditor; }
this.bibliographyDataHasChanged = this.bibliographyHasChanged = true;
return Zotero.Integration.displayDialog(doc,
'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}
*/
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();
}
/**
* 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];
}
}
}
/**
* 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
*/
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;
}
}
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();
}
/**
* 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 prefs = "";
for(var pref in this.prefs) {
prefs += '';
}
return ''+
''+
''+
(prefs ? ''+prefs+'' : '')+'';
};
/**
* Unserializes document-specific XML
*/
Zotero.Integration.DocumentData.prototype.unserializeXML = function(xmlData) {
var parser = Components.classes["@mozilla.org/xmlextras/domparser;1"]
.createInstance(Components.interfaces.nsIDOMParser),
doc = parser.parseFromString(xmlData, "application/xml");
this.sessionID = Zotero.Utilities.xpathText(doc, '/data/session[1]/@id');
this.style = {"styleID":Zotero.Utilities.xpathText(doc, '/data/style[1]/@id'),
"locale":Zotero.Utilities.xpathText(doc, '/data/style[1]/@locale'),
"hasBibliography":(Zotero.Utilities.xpathText(doc, '/data/style[1]/@hasBibliography') == 1),
"bibliographyStyleHasBeenSet":(Zotero.Utilities.xpathText(doc, '/data/style[1]/@bibliographyStyleHasBeenSet') == 1)};
this.prefs = {};
for (let pref of Zotero.Utilities.xpath(doc, '/data/prefs[1]/pref')) {
var name = pref.getAttribute("name");
var value = pref.getAttribute("value");
if(value === "true") {
value = true;
} else if(value === "false") {
value = false;
}
this.prefs[name] = value;
}
if(this.prefs["storeReferences"] === undefined) this.prefs["storeReferences"] = false;
if(this.prefs["automaticJournalAbbreviations"] === undefined) this.prefs["automaticJournalAbbreviations"] = false;
this.zoteroVersion = doc.documentElement.getAttribute("zotero-version");
if(!this.zoteroVersion) this.zoteroVersion = "2.0";
this.dataVersion = doc.documentElement.getAttribute("data-version");
if(!this.dataVersion) this.dataVersion = 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