zotero/chrome/content/zotero/xpcom/integration.js
Simon Kornblith f0f22009c4 - fix (most) unnecessary citation update issues
- switch Zotero.JSON to native Firefox JSON support
- update to citeproc-js 1.0.21

From Frank's announcement:
In this release:

- Tighten up internal "NUMERIC" update_mode to mean styles that render
citation-number in citations (renderings in the bibliography are now
ignored for purposes of setting this flag).

- The numeric styles fix introduced at version 1.0.17 broke with
styles that sort the bibliography on anything other than citation-
number (i.e. document first-reference order).  With this release,
arbitrary sorts of the bibliography work with numeric styles.

- Position evaluation code is now invoked only in styles that make use
of position testing (to save a few cycles).

- Numeric styles now perform targetted citation updates correctly.
2010-06-05 17:49:04 +00:00

1786 lines
58 KiB
JavaScript

/*
***** 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 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 General Public License for more details.
You should have received a copy of the GNU General Public License
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;
Zotero.Integration = new function() {
var _fifoFile = null;
var _osascriptFile;
this.sessions = {};
/**
* Initializes the pipe used for integration on non-Windows platforms.
*/
this.init = function() {
if(!Zotero.isWin) {
// determine directory to put the pipe in
if(Zotero.isMac) {
// 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.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");
}
Zotero.debug("Initializing Zotero integration pipe at "+_fifoFile.path);
// destroy old pipe, if one exists
try {
if(_fifoFile.exists()) {
_fifoFile.remove(false);
}
}
catch (e) {
Zotero.debug("Could not remove old integration pipe", 1);
Components.utils.reportError(
"Zotero word processor integration initialization failed. "
+ "See http://forums.zotero.org/discussion/12054/#Item_10 "
+ "for instructions on correcting this problem."
);
return;
}
// make a new pipe
var mkfifo = Components.classes["@mozilla.org/file/local;1"].
createInstance(Components.interfaces.nsILocalFile);
mkfifo.initWithPath("/usr/bin/mkfifo");
if(!mkfifo.exists()) mkfifo.initWithPath("/bin/mkfifo");
if(!mkfifo.exists()) mkfifo.initWithPath("/usr/local/bin/mkfifo");
if(mkfifo.exists()) {
var main = Components.classes["@mozilla.org/thread-manager;1"].getService().mainThread;
var background = Components.classes["@mozilla.org/thread-manager;1"].getService().newThread(0);
var me = this;
function mainThread(agent, cmd) {
this.agent = agent;
this.cmd = cmd;
}
mainThread.prototype.run = function() {
me.execCommand(this.agent, this.cmd);
}
function fifoThread() {}
fifoThread.prototype.run = function() {
var proc = Components.classes["@mozilla.org/process/util;1"].
createInstance(Components.interfaces.nsIProcess);
proc.init(mkfifo);
proc.run(true, [_fifoFile.path], 1);
if(!_fifoFile.exists()) Zotero.debug("Could not initialize Zotero integration pipe");
var fifoStream = Components.classes["@mozilla.org/network/file-input-stream;1"].
createInstance(Components.interfaces.nsIFileInputStream);
var line = {};
while(true) {
fifoStream.QueryInterface(Components.interfaces.nsIFileInputStream);
fifoStream.init(_fifoFile, -1, 0, 0);
fifoStream.QueryInterface(Components.interfaces.nsILineInputStream);
fifoStream.readLine(line);
fifoStream.close();
var spaceIndex = line.value.indexOf(" ");
var agent = line.value.substr(0, spaceIndex);
var cmd = line.value.substr(spaceIndex+1);
if(agent == "Zotero" && cmd == "shutdown") return;
main.dispatch(new mainThread(agent, cmd), background.DISPATCH_NORMAL);
}
}
fifoThread.prototype.QueryInterface = mainThread.prototype.QueryInterface = function(iid) {
if (iid.equals(Components.interfaces.nsIRunnable) ||
iid.equals(Components.interfaces.nsISupports)) return this;
throw Components.results.NS_ERROR_NO_INTERFACE;
}
background.dispatch(new fifoThread(), background.DISPATCH_NORMAL);
var observerService = Components.classes["@mozilla.org/observer-service;1"]
.getService(Components.interfaces.nsIObserverService);
observerService.addObserver({
observe: me.destroy
}, "quit-application", false);
} else {
Zotero.debug("mkfifo not found -- not initializing integration pipe");
}
}
// initialize SOAP server just to throw version errors
Zotero.Integration.Compat.init();
}
/**
* Executes an integration command.
*/
this.execCommand = function execCommand(agent, command) {
// 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";
Zotero.debug("Integration: Instantiating "+componentClass+" for command "+command);
var application = Components.classes[componentClass]
.getService(Components.interfaces.zoteroIntegrationApplication);
} catch(e) {
Zotero.Integration.activate();
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 create a new document; otherwise display an error using the alert service
try {
var integration = new Zotero.Integration.Document(application);
} catch(e) {
Zotero.Integration.activate();
Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
.getService(Components.interfaces.nsIPromptService)
.alert(null, Zotero.getString("integration.error.title"),
Zotero.getString("integration.error.generic"));
throw e;
}
// Try to execute the command; otherwise display an error in the word processor
try {
integration[command]();
} catch(e) {
if(!(e instanceof Zotero.Integration.UserCancelledException)) {
if(e instanceof Zotero.Integration.DisplayException) {
integration._doc.displayAlert(e.toString(),
Components.interfaces.zoteroIntegrationDocument.DIALOG_ICON_STOP,
Components.interfaces.zoteroIntegrationDocument.DIALOG_BUTTONS_OK);
} else {
// check to see whether there's a pyxpcom error in the console, since it doesn't
// get thrown directly
var throwErr = true;
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;
integration._doc.displayAlert(Zotero.getString("integration.error.generic")+message,
Components.interfaces.zoteroIntegrationDocument.DIALOG_ICON_STOP,
Components.interfaces.zoteroIntegrationDocument.DIALOG_BUTTONS_OK);
Zotero.debug(e);
throw e;
}
}
} finally {
integration.cleanup();
}
}
/**
* Destroys the integration pipe.
*/
this.destroy = function() {
// send shutdown message to fifo thread
var oStream = Components.classes["@mozilla.org/network/file-output-stream;1"].
getService(Components.interfaces.nsIFileOutputStream);
oStream.init(_fifoFile, 0x02 | 0x10, 0, 0);
var cmd = "Zotero shutdown\n";
oStream.write(cmd, cmd.length);
oStream.close();
_fifoFile.remove(false);
}
/**
* Activates Firefox
*/
this.activate = function() {
if(Zotero.isMac) {
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);
if(Zotero.oscpu == "PPC Mac OS X 10.4" || Zotero.oscpu == "Intel Mac OS X 10.4") {
// 10.4 doesn't support "tell application id"
proc.run(false, ['-e', 'tell application "Firefox" to activate'], 2);
} else {
proc.run(false, ['-e', 'tell application id "org.mozilla.firefox" to activate'], 2);
}
}
}
}
}
/**
* 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.";
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.";
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(); }
// Field code for an item
const ITEM_CODE = "ITEM";
// Field code for a bibliography
const BIBLIOGRAPHY_CODE = "BIBL";
// Placeholder for an empty bibliography
const BIBLIOGRAPHY_PLACEHOLDER = "{Bibliography}";
/**
*
*/
Zotero.Integration.Document = function(app) {
this._app = app;
this._doc = app.getActiveDocument();
}
/**
* 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();
session.setData(data);
return session;
}
/**
* Gets preferences for a document
* @param require {Boolean} Whether an error should be thrown if no preferences 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) {
if(require) {
throw new Zotero.Integration.DisplayException("mustInsertCitation");
} else {
// Set doc prefs if no data string yet
this._session = this._createNewSession(new Zotero.Integration.DocumentData());
if(dontRunSetDocPrefs) return false;
Zotero.Integration.activate();
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());
}
} else {
var data = new Zotero.Integration.DocumentData(dataString);
if(data.dataVersion < DATA_VERSION) {
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(Zotero.Integration.sessions[data.sessionID]) {
this._session = Zotero.Integration.sessions[data.sessionID];
} else {
if(data.prefs.fieldType == "Field" && this._app.primaryFieldType != "Field") {
// Converted OOo docs use ReferenceMarks, not fields
data.prefs.fieldType = "ReferenceMark";
}
this._session = this._createNewSession(data);
// make sure style is defined
if(!this._session.style) {
Zotero.Integration.activate();
try {
this._session.setDocPrefs(this._app.primaryFieldType, this._app.secondaryFieldType);
} finally {
this._doc.activate();
}
}
this._doc.setDocumentData(this._session.data.serializeXML());
this._reloadSession = true;
}
}
this._session.resetRequest();
return !!dataString;
}
/**
* Gets all fields for a document
* @param require {Boolean} Whether an error should be thrown if no fields exist
*/
Zotero.Integration.Document.prototype._getFields = function(require) {
if(this._fields) return;
if(!this._session && !this._getSession(require, true)) 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();
Zotero.debug("Got "+this._fields.length+" fields in "+(endTime-getFieldsTime)/1000+"; "+1000/((endTime-getFieldsTime)/this._fields.length)+" fields/second");
if(require && !this._fields.length) {
throw new Zotero.Integration.DisplayException("mustInsertCitation");
}
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'])) {
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;
}
if(!field) {
var field = this._doc.insertField(this._session.data.prefs['fieldType'],
(note ? this._session.data.prefs["noteType"] : 0));
}
return field;
}
/**
* Loads existing citations and bibliographies out of a document, and creates or edits fields
*/
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;
var collectFieldsTime = (new Date()).getTime();
for(var i in this._fields) {
var field = this._fields[i];
if(editField && field.equals(editField)) {
editFieldIndex = i;
} else {
var fieldCode = field.getCode();
if(fieldCode.substr(0, ITEM_CODE.length) == ITEM_CODE) {
var noteIndex = (this._session.styleClass == "note" ? field.getNoteIndex() : 0);
try {
this._session.addCitation(i, noteIndex, fieldCode.substr(ITEM_CODE.length+1));
} 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
Zotero.Integration.activate();
this._session.reselectItem(e);
// Now try again
this._session.addCitation(i, field.getNoteIndex(), fieldCode.substr(ITEM_CODE.length+1));
this._doc.activate();
}
}
} else if(e instanceof Zotero.Integration.CorruptFieldException) {
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();
}
}
} else {
throw e;
}
}
} else if(fieldCode.substr(0, BIBLIOGRAPHY_CODE.length) == BIBLIOGRAPHY_CODE) {
this._bibliographyFields.push(field);
if(!this._session.bibliographyData && !bibliographyData) {
bibliographyData = fieldCode.substr(BIBLIOGRAPHY_CODE.length+1);
}
} else if(fieldCode == "TEMP") {
if(newField) {
editFieldIndex = i;
editField = field;
} else {
this._deleteFields.push(i);
}
}
}
}
var endTime = (new Date()).getTime();
Zotero.debug("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.updateCitations();
this._session.updateIndices = {};
this._session.updateItemIDs = {};
this._session.bibliographyHasChanged = false;
}
// create new citation or edit existing citation
if(editFieldIndex) {
var editFieldCode = editField.getCode().substr(ITEM_CODE.length+1);
var editCitation = editFieldCode ? this._session.unserializeCitation(editFieldCode, editFieldIndex) : null;
Zotero.Integration.activate();
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
*/
Zotero.Integration.Document.prototype._updateDocument = function(forceCitations, forceBibliography) {
// update citations
this._session.updateUpdateIndices(forceCitations);
this._deleteFields = this._deleteFields.concat(this._session.updateCitations());
for(var i in this._session.citationText) {
citation = this._session.citationsByIndex[i];
if(!citation) continue;
var fieldCode = this._session.getCitationField(citation);
if(fieldCode != citation.properties.field) {
this._fields[citation.properties.index].setCode(ITEM_CODE+" "+fieldCode);
}
if(citation.properties.custom) {
var citationText = citation.properties.custom;
} else {
var citationText = this._session.citationText[i];
}
if(citationText.indexOf("\\") !== -1) {
// need to set text as RTF
this._fields[citation.properties.index].setText("{\\rtf "+citationText+"}", true);
} else {
// set text as plain
this._fields[citation.properties.index].setText(citationText, false);
}
}
// 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(this._session.bibliographyDataHasChanged) {
var bibliographyData = this._session.getBibliographyData();
for each(var field in this._bibliographyFields) {
field.setCode(BIBLIOGRAPHY_CODE+" "+bibliographyData);
}
}
var bibliographyText = this._session.getBibliography();
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
this._deleteFields.sort();
for(var i=(this._deleteFields.length-1); i>=0; i--) {
this._fields[this._deleteFields[i]].delete();
}
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() {
this._getSession();
var field = this._addField(true);
if(!field) return;
field.setCode("TEMP");
this._updateSession(true);
this._updateDocument();
}
/**
* Edits the citation at the cursor position.
*/
Zotero.Integration.Document.prototype.editCitation = function() {
this._getSession(true);
var field = this._doc.cursorInField(this._session.data.prefs['fieldType'])
if(!field) {
throw new Zotero.Integration.DisplayException("notInCitation");
}
this._updateSession(false, field);
this._updateDocument(false, false);
}
/**
* Adds a bibliography to the current document.
*/
Zotero.Integration.Document.prototype.addBibliography = function() {
this._getSession(true);
// Make sure we can have a bibliography
if(!this._session.data.style.hasBibliography) {
throw new Zotero.Integration.DisplayException("noBibliography");
}
// Make sure we have some citations
this._getFields(true);
var field = this._addField();
if(!field) return;
var bibliographyData = this._session.getBibliographyData();
field.setCode(BIBLIOGRAPHY_CODE+" "+bibliographyData);
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
this._getFields(true);
var haveBibliography = false;
for(var i=this._fields.length-1; i>=0; i--) {
if(this._fields[i].getCode().substr(0, BIBLIOGRAPHY_CODE.length) == BIBLIOGRAPHY_CODE) {
haveBibliography = true;
break;
}
}
if(!haveBibliography) {
throw new Zotero.Integration.DisplayException("mustInsertBibliography");
}
this._updateSession();
Zotero.Integration.activate();
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() {
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() {
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();
}
}
}
/**
* Displays a dialog to set document preferences (style, footnotes/endnotes, etc.)
*/
Zotero.Integration.Document.prototype.setDocPrefs = function() {
this._getFields();
Zotero.Integration.activate();
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();
if(convertItems && fieldCode.substr(0, ITEM_CODE.length) == ITEM_CODE) {
fieldsToConvert.push(field);
fieldNoteTypes.push(this._session.data.prefs.noteType);
} else if(convertBibliographies && fieldCode.substr(0, BIBLIOGRAPHY_CODE.length) == BIBLIOGRAPHY_CODE) {
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;
}
// refresh contents
this.refresh();
}
}
}
/**
* 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
*/
Zotero.Integration.Session = function() {
// holds items not in document that should be in bibliography
this.uncitedItems = new Object();
this.customBibliographyText = new Object();
this.reselectedItems = new Object();
this.citationIDs = new Object();
}
/**
* Resets per-request variables in the CitationSet
*/
Zotero.Integration.Session.prototype.resetRequest = function() {
this.citationsByItemID = new Object();
this.citationsByIndex = new Array();
this.uriMap = new Zotero.Integration.URIMap(this);
this.regenerateAll = false;
this.bibliographyHasChanged = false;
this.bibliographyDataHasChanged = false;
this.updateItemIDs = new Object();
this.updateIndices = new Object();
this.newIndices = new Object();
this.oldCitationIDs = this.citationIDs;
this.citationIDs = new Object();
this.citationText = new Object();
}
/**
* Changes the Session style and data
* @param data {Zotero.Integration.DocumentData}
*/
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.debug(e)
data.style.styleID = undefined;
return false;
}
return true;
}
return false;
}
/**
* 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;
}
var window = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
.getService(Components.interfaces.nsIWindowWatcher)
.openWindow(null, 'chrome://zotero/content/integration/integrationDocPrefs.xul', '',
'chrome,centerscreen', io, true);
while(!window.closed) Zotero.mainThread.processNextEvent(true);
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;
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;
var window = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
.getService(Components.interfaces.nsIWindowWatcher)
.openWindow(null,'chrome://zotero/content/selectItemsDialog.xul', '',
'chrome,centerscreen,resizable', io, true);
while(!window.closed) Zotero.mainThread.processNextEvent(true);
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
*/
Zotero.Integration.Session._acceptableTypes = ["string", "boolean", "number"];
Zotero.Integration.Session._saveProperties = ["custom", "sort"];
Zotero.Integration.Session._saveItems = ["locator", "label", "suppress-author", "author-only", "prefix", "suffix"];
Zotero.Integration.Session.prototype.getCitationField = function(citation) {
var type;
var field = [];
field.push('"citationID":'+uneval(citation.citationID));
var properties = [];
for(var j=0; j<Zotero.Integration.Session._saveProperties.length; j++) {
var property = Zotero.Integration.Session._saveProperties[j];
if(citation.properties[property] || citation.properties[property] === false) {
let propval = typeof citation.properties[property] == "object" ?
Zotero.JSON.serialize(citation.properties[property]) :
uneval(citation.properties[property]);
properties.push('"'+property+'":'+propval);
}
}
if(properties.length) field.push('"properties":{'+properties.join(",")+"}");
var citationItems = [];
for(var j=0; j<citation.citationItems.length; j++) {
var citationItem = [];
// save citationItem properties
for(var k in citation.citationItems[j]) {
type = typeof(citation.citationItems[j][k]);
if(citation.citationItems[j][k] && Zotero.Integration.Session._saveItems.indexOf(k) !== -1
&& Zotero.Integration.Session._acceptableTypes.indexOf(type) !== -1) {
let propval = typeof citation.citationItems[j][k] == "object" ?
Zotero.JSON.serialize(citation.citationItems[j][k]) :
uneval(citation.citationItems[j][k]);
citationItem.push('"'+k+'":'+propval);
}
}
// save URI
citationItem.push('"uri":'+Zotero.JSON.serialize(this.uriMap.getURIsForItemID(citation.citationItems[j].id)));
citationItems.push("{"+citationItem.join(",")+"}");
}
field.push('"citationItems":['+citationItems.join(",")+"]");
return "{"+field.join(",")+"}";
}
/**
* Adds a citation based on a serialized Word field
*/
Zotero.Integration._oldCitationLocatorMap = {
p:"page",
g:"paragraph",
l:"line"
};
/**
* Adds a citation to the arrays representing the document
*/
Zotero.Integration.Session.prototype.addCitation = function(index, noteIndex, arg) {
var index = parseInt(index, 10);
if(typeof(arg) == "string") { // text field
if(arg == "!" || arg == "X") return;
var citation = this.unserializeCitation(arg, index);
} else { // a citation already
var citation = arg;
}
// get items
for(var i=0; i<citation.citationItems.length; i++) {
var citationItem = citation.citationItems[i];
// get Zotero item
var zoteroItem = false;
if(citationItem.uri) {
[zoteroItem, needUpdate] = this.uriMap.getZoteroItemForURIs(citationItem.uri);
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.uri) {
var reselectKeys = citationItem.uri;
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 not already reselected, throw a MissingItemException
if(!zoteroItem) {
throw(new Zotero.Integration.MissingItemException(
reselectKeys, reselectKeyType, i, citation.citationItems.length));
}
}
citationItem.id = zoteroItem.id;
}
citation.properties.added = true;
citation.properties.index = 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.index < 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.index < index && j<byItemID.length-1; j++) {}
byItemID.splice(j++, 0, citation);
}
}
}
var needNewID = !citation.citationID || this.citationIDs[citation.citationID];
if(needNewID || !this.oldCitationIDs[citation.citationID]) {
if(needNewID) {
Zotero.debug("Zotero.Integration: "+citation.citationID+" ("+index+") needs new citationID");
citation.citationID = Zotero.randomString();
}
this.newIndices[index] = true;
this.updateIndices[index] = true;
}
Zotero.debug("Zotero.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) {
if(arg[0] == "{") { // JSON field
// create citation
var citation = {};
var saveCode = true;
// fix for corrupted fields
var lastBracket = arg.lastIndexOf("}");
if(lastBracket+1 != arg.length) {
saveCode = false;
this.updateIndices[index] = true;
arg = arg.substr(0, lastBracket+1);
}
// get JSON
try {
var object = Zotero.JSON.unserialize(arg);
} catch(e) {
try {
var object = Zotero.JSON.unserialize(arg.substr(0, arg.length-1));
} catch(e) {
throw new Zotero.Integration.CorruptFieldException(arg);
}
}
// fix for uppercase citation codes
if(object.CITATIONITEMS) {
saveCode = false;
this.updateIndices[index] = true;
object.citationItems = [];
for (var i=0; i<object.CITATIONITEMS.length; i++) {
for (var j in object.CITATIONITEMS[i]) {
switch (j) {
case 'ITEMID':
var field = 'itemID';
break;
// 'position', 'custom'
default:
var field = j.toLowerCase();
}
if (!object.citationItems[i]) {
object.citationItems[i] = {};
}
object.citationItems[i][field] = object.CITATIONITEMS[i][j];
}
}
}
if(!citation.properties) citation.properties = {};
// copy properties
for(var i in object) {
if(Zotero.Integration.Session._saveProperties.indexOf(i) != -1) {
citation.properties[i] = object[i];
} else if(i == "locatorType") {
citation["label"] = object["locatorType"];
this.updateIndices[index] = true;
} else if(i == "suppressAuthor") {
citation["suppress-author"] = object["suppressAuthor"];
this.updateIndices[index] = true;
} else {
citation[i] = object[i];
}
}
if(!citation.citationID) citation.citationID = Zotero.randomString();
if(saveCode) citation.properties.field = arg;
} 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]};
if(locators) {
citationItem.locator = locators[i].substr(1);
citationItem.label = Zotero.Integration._oldCitationLocatorMap[locators[i][0]];
}
citationItems.push(citationItem);
}
var citation = {"citationItems":citationItems, properties:{}};
this.updateIndices[index] = true;
}
return citation;
}
/**
* marks a citation for removal
*/
Zotero.Integration.Session.prototype.deleteCitation = function(index) {
var oldCitation = (this.citationsByIndex[index] ? this.citationsByIndex[index] : false);
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("Zotero.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() {
// use real RTF, but chop off the first \n
return Zotero.Cite.makeFormattedBibliography(this.style, "rtf", this.customBibliographyText);
}
/**
* Calls CSL.Engine.updateUncitedItems() to reconcile list of uncited items
*/
Zotero.Integration.Session.prototype.updateUncitedItems = function() {
this.style.updateUncitedItems([parseInt(i) for(i in this.uncitedItems)]);
}
/**
* 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=0; i<this.citationsByIndex.length; i++) {
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.citationsByItemID[i].length; j++) {
this.updateIndices[this.citationsByItemID[i][j].properties.index] = true;
}
}
}
}
}
/**
* Returns a formatted citation
*/
Zotero.Integration.Session.prototype.formatCitation = function(index, citation) {
if(!this.citationText[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);
}
}
//Zotero.debug("style.processCitationCluster("+citation.toSource()+", "+citationsPre.toSource()+", "+citationsPost.toSource());
var newCitations = this.style.processCitationCluster(citation, citationsPre, citationsPost);
for each(var newCitation in newCitations) {
Zotero.debug("Zotero.Integration: Citation "+citationIndices[newCitation[0]]+" needs to be updated");
this.citationText[citationIndices[newCitation[0]]] = newCitation[1];
this.updateIndices[citationIndices[newCitation[0]]] = true;
}
// this is a heuristic: if other citations get updated, then we should update the
// bibliography. it would be nice if citeproc-js gave us a better hint about this
return (newCitations.length > 1);
}
}
/**
* 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;
}
}
}
}*/
Zotero.debug("Zotero.Integration: indices of new citations");
Zotero.debug([key for(key in this.newIndices)]);
Zotero.debug("Zotero.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) {
if(deleteCitations.indexOf(index) == -1) deleteCitations.push(index);
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;
}
/**
* Loads document data from a JSON object
*/
Zotero.Integration.Session.prototype.loadBibliographyData = function(json) {
try {
var documentData = Zotero.JSON.unserialize(json);
} catch(e) {
try {
var documentData = Zotero.JSON.unserialize(json.substr(0, json.length-1));
} catch(e) {
throw new Zotero.Integration.CorruptFieldException(json);
}
}
// set uncited
if(documentData.uncited) {
if(documentData.uncited[0]) {
// new style array of arrays with URIs
var zoteroItem, needUpdate;
for each(var uris in documentData.uncited) {
[zoteroItem, needUpdate] = this.uriMap.getZoteroItemForURIs(uris);
if(zoteroItem) this.uncitedItems[zoteroItem.id] = true;
if(needUpdate) this.bibliographyDataHasChanged = true;
}
} 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();
}
// 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;
}
}
this.bibliographyData = json;
}
/**
* Saves document data from a JSON object
*/
Zotero.Integration.Session.prototype.getBibliographyData = function() {
var bibliographyData = {};
// 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));
}
}
// 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 Zotero.JSON.serialize(bibliographyData);
} else {
return ""; // nothing
}
}
/**
* Returns a preview, given a citation object (whose citationItems lack item
* and position)
*/
Zotero.Integration.Session.prototype.previewCitation = function(citation) {
// remove cached citation text
this.citationText = {};
this.addCitation(citation.properties.index, citation.properties.noteIndex, citation);
// add citation items
try {
this.formatCitation(citation.properties.index, citation);
} catch(e) {
Zotero.debug(e);
this.deleteCitation(citation.properties.index);
throw e;
}
this.deleteCitation(citation.properties.index);
return this.citationText[citation.properties.index];
}
/**
* 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.uri) {
[zoteroItem, ] = this.uriMap.getZoteroItemForURIs(citationItem.uri);
} else if(citationItem.key) {
zoteroItem = Zotero.Items.getByKey(citationItem.key);
}
if(zoteroItem) citationItem.id = zoteroItem.id;
}
}
}
// create object to hold citation
io.citation = (citation ? Zotero.JSON.unserialize(Zotero.JSON.serialize(citation)) : {"citationItems":{}, "properties":{}});
io.citation.properties.index = parseInt(index, 10);
io.citation.properties.noteIndex = parseInt(noteIndex, 10);
// assign preview function
io.previewFunction = function() {
return me.previewCitation(io.citation);
}
var window = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
.getService(Components.interfaces.nsIWindowWatcher)
.openWindow(null, 'chrome://zotero/content/integration/addCitationDialog.xul', '',
'chrome,centerscreen,resizable', io);
while(!window.closed) Zotero.mainThread.processNextEvent(true);
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;
var window = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
.getService(Components.interfaces.nsIWindowWatcher)
.openWindow(null, 'chrome://zotero/content/integration/editBibliographyDialog.xul', '',
'chrome,centerscreen,resizable', io, true);
while(!window.closed) Zotero.mainThread.processNextEvent(true);
}
/**
* @class Interface for bibliography editor to alter document bibliography
* @constructor
* Creates a new bibliography editor interface
* @param {Zotero.Integration.Session} session
*/
Zotero.Integration.Session.BibliographyEditInterface = function(session) {
this.session = session;
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();
for(var i in this.bibliography[0].entry_ids) {
if(this.session.customBibliographyText[this.bibliography[0].entry_ids[i]]) {
this.bibliography[1][i] = this.session.customBibliographyText[this.bibliography[0].entry_ids[i]];
}
}
}
/**
* Checks whether an item ID is cited in the bibliography being edited
*/
Zotero.Integration.Session.BibliographyEditInterface.prototype.isCited = function(item) {
if(this.session.citationsByItemID[item]) return true;
return false;
}
/**
* Adds an item to the bibliography
*/
Zotero.Integration.Session.BibliographyEditInterface.prototype.add = function(itemID) {
// create new item
this.session.uncitedItems[itemID] = true;
this._update();
}
/**
* Removes an item from the bibliography being edited
*/
Zotero.Integration.Session.BibliographyEditInterface.prototype.remove = function(itemID) {
// delete citations if necessary
if(this.session.citationsByItemID[itemID]) {
for(var j=0; j<this.session.citationsByItemID[itemID].length; j++) {
var citation = this.session.citationsByItemID[itemID][j];
this.session.updateIndices[citation.properties.index] = true;
citation.properties["delete"] = true;
}
delete this.session.citationsByItemID[itemID];
this.session.updateCitations();
}
// delete uncited if neceessary
if(this.session.uncitedItems[itemID]) delete this.session.uncitedItems[itemID];
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}/>
<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)};
this.prefs = {};
for each(var pref in xmlData.prefs.children()) {
this.prefs[pref.@name.toString()] = pref.@value.toString();
}
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")};
this.prefs = {"fieldType":((prefParameters[5] == "1" || prefParameters[5] == "True") ? "Bookmark" : "Field")};
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))];
}
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 in uris) {
try {
zoteroItem = Zotero.URI.getURIItem(uris[i]);
if(zoteroItem) break;
} catch(e) {}
}
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];
}