zotero/chrome/content/zotero/xpcom/integration.js
Simon Kornblith bd0a4a3b96 Use citeproc-js as CSL parser. There is an issue with multiple citations in numbered styles that I will probably need Frank's help to track down, but otherwise, this seems to work. Many things are completely untested, including EndNote style support. Currently requires CSL 1.0 styles. In the near future, I hope to use the XSLT stylesheet to transform CSL 0.8 to CSL 1.0.
closes #1650: suppress author does not work for multiple sources
closes #1505: Edit Biblography Button Strips Year Disambiguation
closes #1503: Editing a bibliography resets all reference numbers to 1 (new)
closes #1262: Broken pluralization with et al. + other issues
closes #1238: Localize quotation marks
closes #1191: Harmonize 'plural/pluralize' label attribute with CSL schema
closes #1154: Only one works page numbers are added to the citation are when citing multiple works by the same author
closes #1097: Disambiguation issues
closes #1083: Defect in IEEE CSL with Multiple Citations
closes #993: more sophisticated subsequent-author-substitute
closes #833: text-transform doesn't work with name
2010-05-30 11:25:25 +00:00

1776 lines
No EOL
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");
// if we are reloading this session, assume no item IDs to be updated except for edited items
if(this._reloadSession) {
this._session.updateItemIDs = {};
this._session.bibliographyHasChanged = false;
}
// 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;
}
}
}
//this._session.updateItems();
this._session.updateCitations();
// 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);
}
}
}
this._session.updateItems();
}
/**
* Updates bibliographies and fields within a document
*/
Zotero.Integration.Document.prototype._updateDocument = function(forceCitations, forceBibliography) {
// update bibliographies
var output = new Array();
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);
}
}
}
// 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);
}
}
// 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;
Zotero.debug("style is "+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.regenerateAll = this.bibliographyHasChanged = true;
}
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":'+Zotero.JSON.serialize(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) {
properties.push('"'+property+'":'+Zotero.JSON.serialize(citation.properties[property]));
}
}
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) {
citationItem.push('"'+k+'":'+Zotero.JSON.serialize(citation.citationItems[j][k]));
}
}
// 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);
}
}
}
if(!citation.citationID) {
this.newIndices[index] = true;
this.updateIndices[index] = true;
} else if(!this.oldCitationIDs[citation.citationID]) {
this.newIndices[index] = true;
this.updateIndices[index] = true;
}
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] = {properties:{"delete":true}};
if(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];
}
}
}
}
}
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.updateItems() to reconcile item list with current items in document
*/
Zotero.Integration.Session.prototype.updateItems = function() {
var items = [[i, this.citationsByItemID[i][0]] for(i in this.citationsByItemID)
if(this.citationsByItemID[i] && this.citationsByItemID[i].length &&
this.citationsByItemID[i].some(function(citation) citation.properties && !citation.properties.delete))];
items.sort(function(a, b) {
// if first citation of each in different citations, use citation index
if(a[1].properties.index != b[1].properties.index) {
return a[1].properties.index-b[1].properties.index;
}
// if a and b were both first cited in the same citation, look for index in the citation
for each(var citationItem in a[1].citationItems) {
if(citationItem.id == a[0]) {
return -1;
} else if(citationItem.id == b[0]) {
return 1;
}
}
// should never happen
Zotero.debug("WARNING: Zotero.Integration.Session.updateItems sort function returned 0");
return 0;
});
// get rid of the second part of the items (the first citation, used for sort purposes)
// and also add in the uncited items
items = [parseInt(item[0]) for each(item in items)].concat([parseInt(i) for(i in this.uncitedItems)]);
//Zotero.debug("items are ");
//Zotero.debug(items);
// set items in the bibliography
this.style.updateItems(items);
}
/**
* 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.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]) {
//this.updateItems();
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(citation);
//Zotero.debug(citationsPre);
//Zotero.debug(citationsPost);
//Zotero.debug(this.style.registry.registry.toSource());
var newCitations = this.style.processCitationCluster(citation, citationsPre, citationsPost);
for each(var newCitation in newCitations) {
this.citationText[citationIndices[newCitation[0]]] = newCitation[1];
}
// 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() {
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, 10);
var citation = this.citationsByIndex[index];
if(citation.properties.delete) {
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];
}
}
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;
}
}
// 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) {
// add citation items
this.addCitation(citation.properties.index, citation.properties.noteIndex, citation);
//this.updateItems();
this.formatCitation(citation.properties.index, citation);
this.deleteCitation(citation.properties.index);
var citationText = this.citationText[citation.properties.index];
delete this.citationText[citation.properties.index];
return citationText;
}
/**
* 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(citation && !io.citation.citationItems.length) {
io.citation = citation;
}
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.updateItems();
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];
}
// 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];
}