diff --git a/chrome/content/zotero/bibliography.js b/chrome/content/zotero/bibliography.js
index 1ff15220e..b5ee9af4b 100644
--- a/chrome/content/zotero/bibliography.js
+++ b/chrome/content/zotero/bibliography.js
@@ -106,15 +106,11 @@ var Zotero_File_Interface_Bibliography = new function() {
styleChanged(selectIndex);
}
if(document.getElementById("formatUsing")) {
- if(_io.useBookmarks && _io.useBookmarks == 1) document.getElementById("formatUsing").selectedIndex = 1;
- if(_io.openOffice) {
- var formatOption = "referenceMarks";
- } else {
- var formatOption = "fields";
- }
+ if(_io.fieldType == "Bookmarks") document.getElementById("formatUsing").selectedIndex = 1;
+ Zotero.safeDebug(_io)
+ var formatOption = (_io.primaryFieldType == "ReferenceMark" ? "referenceMarks" : "fields");
document.getElementById("fields").label = Zotero.getString("integration."+formatOption+".label");
document.getElementById("fields-caption").textContent = Zotero.getString("integration."+formatOption+".caption");
- document.getElementById("fields-caption").textContent = Zotero.getString("integration."+formatOption+".caption");
document.getElementById("fields-file-format-notice").textContent = Zotero.getString("integration."+formatOption+".fileFormatNotice");
document.getElementById("bookmarks-file-format-notice").textContent = Zotero.getString("integration.fields.fileFormatNotice");
}
@@ -170,7 +166,7 @@ var Zotero_File_Interface_Bibliography = new function() {
// ONLY FOR integrationDocPrefs.xul: collect displayAs
if(document.getElementById("displayAs")) {
_io.useEndnotes = document.getElementById("displayAs").selectedIndex;
- _io.useBookmarks = document.getElementById("formatUsing").selectedIndex;
+ _io.fieldType = (document.getElementById("formatUsing").selectedIndex == 0 ? _io.primaryFieldType : _io.secondaryFieldType);
}
// save style (this happens only for "Export Bibliography," or Word
diff --git a/chrome/content/zotero/xpcom/integration.js b/chrome/content/zotero/xpcom/integration.js
index 55d9b4f0c..4d0771a79 100644
--- a/chrome/content/zotero/xpcom/integration.js
+++ b/chrome/content/zotero/xpcom/integration.js
@@ -1,545 +1,747 @@
/*
***** BEGIN LICENSE BLOCK *****
-
- Copyright (c) 2006 Center for History and New Media
- George Mason University, Fairfax, Virginia, USA
- http://chnm.gmu.edu
-
- Licensed under the Educational Community License, Version 1.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.opensource.org/licenses/ecl1.php
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
+
+ Copyright (c) 2009 Center for History and New Media
+ George Mason University, Fairfax, Virginia, USA
+ http://chnm.gmu.edu
+
+ This program 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.
+
+ This program 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 this program. If not, see .
***** END LICENSE BLOCK *****
*/
-const API_VERSION = 2;
-const COMPAT_API_VERSION = 6;
+const RESELECT_KEY_URI = 1;
+const RESELECT_KEY_ITEM_KEY = 2;
+const RESELECT_KEY_ITEM_ID = 3;
Zotero.Integration = new function() {
- var _contentLengthRe = /[\r\n]Content-Length: *([0-9]+)/i;
- var _XMLRe = /<\?[^>]+\?>/;
- var _onlineObserverRegistered;
+ var _fifoFile, _osascriptFile;
this.sessions = {};
- var ns = "http://www.zotero.org/namespaces/SOAP";
- this.ns = new Namespace(ns);
-
- this.init = init;
- this.handleHeader = handleHeader;
- this.handleEnvelope = handleEnvelope;
-
this.__defineGetter__("usePopup", function () {
return Zotero.isWin && !Zotero.Prefs.get("integration.realWindow");
});
- /*
- * initializes a very rudimentary web server used for SOAP RPC
+ /**
+ * Initializes the pipe used for integration on non-Windows platforms.
*/
- function init() {
- this.env = new Namespace("http://schemas.xmlsoap.org/soap/envelope/");
-
- if (Zotero.Utilities.HTTP.browserIsOffline()) {
- Zotero.debug('Browser is offline -- not initializing integration HTTP server');
- _registerOnlineObserver()
- return;
- }
-
- // start listening on socket
- var serv = Components.classes["@mozilla.org/network/server-socket;1"]
- .createInstance(Components.interfaces.nsIServerSocket);
- try {
- // bind to a random port on loopback only
- serv.init(Zotero.Prefs.get('integration.port'), true, -1);
- serv.asyncListen(Zotero.Integration.SocketListener);
+ this.init = function() {
+ if(!Zotero.isWin) {
+ // create a new file representing the pipe
+ _fifoFile = Components.classes["@mozilla.org/file/directory_service;1"].
+ getService(Components.interfaces.nsIProperties).
+ get("Home", Components.interfaces.nsIFile);
+ _fifoFile.append(".zoteroIntegrationPipe");
- Zotero.debug("Integration HTTP server listening on 127.0.0.1:"+serv.port);
- } catch(e) {
- Zotero.debug("Not initializing integration HTTP server");
- }
-
- _registerOnlineObserver()
- }
-
- /*
- * handles an HTTP request
- */
- function handleHeader(header) {
- // get first line of request (all we care about for now)
- var method = header.substr(0, header.indexOf(" "));
-
- if(!method) {
- return _generateResponse("400 Bad Request");
- }
-
- if(method != "POST") {
- return _generateResponse("501 Method Not Implemented");
- } else {
- // parse content length
- var m = _contentLengthRe.exec(header);
- if(!m) {
- return _generateResponse("400 Bad Request");
- } else {
- return parseInt(m[1]);
- }
- }
- }
-
- /*
- * handles a SOAP envelope
- */
- function handleEnvelope(envelope) {
- Zotero.debug("Integration: SOAP Request\n"+envelope);
- envelope = envelope.replace(_XMLRe, "");
- var env = this.env;
-
- var xml = new XML(envelope);
- var request = xml.env::Body.children()[0];
- if(request.namespace() != this.ns) {
- Zotero.debug("Integration: SOAP method not supported: invalid namespace");
- } else if(!xml.env::Header.children().length()) {
- // old style SOAP request
- var name = request.localName();
- if(Zotero.Integration.SOAP_Compat[name]) {
- if(request.input.length()) {
- // split apart passed parameters (same colon-escaped format
- // as we pass)
- var input = request.input.toString();
- var vars = new Array();
- vars[0] = "";
- var i = 0;
+ // destroy old pipe, if one exists
+ if(_fifoFile.exists()) _fifoFile.remove(false);
+
+ // 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);
- var lastIndex = 0;
- var colonIndex = input.indexOf(":", lastIndex);
- while(colonIndex != -1) {
- if(input[colonIndex+1] == ":") { // escaped
- vars[i] += input.substring(lastIndex, colonIndex+1);
- lastIndex = colonIndex+2;
- } else { // not escaped
- vars[i] += input.substring(lastIndex, colonIndex);
- i++;
- vars[i] = "";
- lastIndex = colonIndex+1;
- }
- colonIndex = input.indexOf(":", lastIndex);
+ 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);
}
- vars[i] += input.substr(lastIndex);
- } else {
- var vars = null;
}
- // execute request
- var output = Zotero.Integration.SOAP_Compat[name](vars);
-
- // ugh: we can't use real SOAP, since AppleScript VBA can't pass
- // objects, so implode arrays
- if(!output) {
- output = "";
+ 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;
}
- if(typeof(output) == "object") {
- for(var i in output) {
- if(typeof(output[i]) == "string") {
- output[i] = output[i].replace(/:/g, "::");
- }
- }
- output = output.join(":");
- }
-
- // create envelope
- var responseEnvelope =
-
-
-
-
-
- ;
-
- var response = '\n'+responseEnvelope.toXMLString();
- Zotero.debug("Integration: SOAP Response\n"+response);
-
- // return OK
- return _generateResponse("200 OK", 'text/xml; charset="UTF-8"',
- response);
+ 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("Integration: SOAP method not supported");
+ Zotero.debug("mkfifo not found -- not initializing integration pipe");
}
- } else {
- // execute request
- request = new Zotero.Integration.Request(xml);
- return _generateResponse(request.status+" "+request.statusText,
- 'text/xml; charset="UTF-8"', request.responseText);
}
+
+ // initialize SOAP server just to throw version errors
+ Zotero.Integration.Compat.init();
}
- /*
- * generates the response to an HTTP request
+ /**
+ * Executes an integration command.
*/
- function _generateResponse(status, contentType, body) {
- var response = "HTTP/1.0 "+status+"\r\n";
-
- if(body) {
- if(contentType) {
- response += "Content-Type: "+contentType+"\r\n";
- }
- response += "\r\n"+body;
- } else {
- response += "Content-Length: 0\r\n\r\n"
- }
-
- return response;
- }
-
-
- function _registerOnlineObserver() {
- if (_onlineObserverRegistered) {
- return;
- }
-
- // Observer to enable the integration when we go online
- var observer = {
- observe: function(subject, topic, data) {
- if (data == 'online') {
- Zotero.Integration.init();
- }
- }
- };
-
- var observerService =
- Components.classes["@mozilla.org/observer-service;1"]
- .getService(Components.interfaces.nsIObserverService);
- observerService.addObserver(observer, "network:offline-status-changed", false);
-
- _onlineObserverRegistered = true;
- }
-}
-
-Zotero.Integration.SocketListener = new function() {
- this.onSocketAccepted = onSocketAccepted;
- this.onStopListening = onStopListening;
-
- /*
- * called when a socket is opened
- */
- function onSocketAccepted(socket, transport) {
- // get an input stream
- var iStream = transport.openInputStream(0, 0, 0);
- var oStream = transport.openOutputStream(Components.interfaces.nsITransport.OPEN_BLOCKING, 0, 0);
-
- var dataListener = new Zotero.Integration.DataListener(iStream, oStream);
- var pump = Components.classes["@mozilla.org/network/input-stream-pump;1"]
- .createInstance(Components.interfaces.nsIInputStreamPump);
- pump.init(iStream, -1, -1, 0, 0, false);
- pump.asyncRead(dataListener, null);
- }
-
- function onStopListening(serverSocket, status) {
- Zotero.debug("Integration HTTP server going offline");
- }
-}
-
-/*
- * handles the actual acquisition of data
- */
-Zotero.Integration.DataListener = function(iStream, oStream) {
- this.header = "";
- this.headerFinished = false;
-
- this.body = "";
- this.bodyLength = 0;
-
- this.iStream = iStream;
- this.oStream = oStream;
- this.sStream = Components.classes["@mozilla.org/scriptableinputstream;1"]
- .createInstance(Components.interfaces.nsIScriptableInputStream);
- this.sStream.init(iStream);
-
- this.foundReturn = false;
-}
-
-/*
- * called when a request begins (although the request should have begun before
- * the DataListener was generated)
- */
-Zotero.Integration.DataListener.prototype.onStartRequest = function(request, context) {}
-
-/*
- * called when a request stops
- */
-Zotero.Integration.DataListener.prototype.onStopRequest = function(request, context, status) {
- this.iStream.close();
- this.oStream.close();
-}
-
-/*
- * called when new data is available
- */
-Zotero.Integration.DataListener.prototype.onDataAvailable = function(request, context,
- inputStream, offset, count) {
- var readData = this.sStream.read(count);
-
- if(this.headerFinished) { // reading body
- this.body += readData;
- // check to see if data is done
- this._bodyData();
- } else { // reading header
- // see if there's a magic double return
- var lineBreakIndex = readData.indexOf("\r\n\r\n");
- if(lineBreakIndex != -1) {
- if(lineBreakIndex != 0) {
- this.header += readData.substr(0, lineBreakIndex+4);
- this.body = readData.substr(lineBreakIndex+4);
- }
-
- this._headerFinished();
- return;
- }
- var lineBreakIndex = readData.indexOf("\n\n");
- if(lineBreakIndex != -1) {
- if(lineBreakIndex != 0) {
- this.header += readData.substr(0, lineBreakIndex+2);
- this.body = readData.substr(lineBreakIndex+2);
- }
-
- this._headerFinished();
- return;
- }
- if(this.header && this.header[this.header.length-1] == "\n" &&
- (readData[0] == "\n" || readData[0] == "\r")) {
- if(readData.length > 1 && readData[1] == "\n") {
- this.header += readData.substr(0, 2);
- this.body = readData.substr(2);
- } else {
- this.header += readData[0];
- this.body = readData.substr(1);
- }
-
- this._headerFinished();
- return;
- }
- this.header += readData;
- }
-}
-
-/*
- * processes an HTTP header and decides what to do
- */
-Zotero.Integration.DataListener.prototype._headerFinished = function() {
- this.headerFinished = true;
- var output = Zotero.Integration.handleHeader(this.header);
-
- if(typeof(output) == "number") {
- this.bodyLength = output;
- // check to see if data is done
- this._bodyData();
- } else {
- this._requestFinished(output);
- }
-}
-
-/*
- * checks to see if Content-Length bytes of body have been read and, if they
- * have, processes the body
- */
-Zotero.Integration.DataListener.prototype._bodyData = function() {
- if(this.body.length >= this.bodyLength) {
- // convert to UTF-8
- var dataStream = Components.classes["@mozilla.org/io/string-input-stream;1"]
- .createInstance(Components.interfaces.nsIStringInputStream);
- dataStream.setData(this.body, this.bodyLength);
-
- var utf8Stream = Components.classes["@mozilla.org/intl/converter-input-stream;1"]
- .createInstance(Components.interfaces.nsIConverterInputStream);
- utf8Stream.init(dataStream, "UTF-8", 4096, "?");
-
- this.body = "";
- var string = {};
- while(utf8Stream.readString(this.bodyLength, string)) {
- this.body += string.value;
- }
-
- // handle envelope
- var output = Zotero.Integration.handleEnvelope(this.body);
- this._requestFinished(output);
- }
-}
-
-/*
- * returns HTTP data from a request
- */
-Zotero.Integration.DataListener.prototype._requestFinished = function(response) {
- // close input stream
- this.iStream.close();
-
- // open UTF-8 converter for output stream
- var intlStream = Components.classes["@mozilla.org/intl/converter-output-stream;1"]
- .createInstance(Components.interfaces.nsIConverterOutputStream);
-
- // write
- try {
- intlStream.init(this.oStream, "UTF-8", 1024, "?".charCodeAt(0));
-
- // write response
- intlStream.writeString(response);
- } finally {
- intlStream.close();
- }
-}
-
-Zotero.Integration.Request = function(xml) {
- var env = Zotero.Integration.env;
- this.header = xml.env::Header;
- this.body = xml.env::Body;
-
- this.responseXML =
-
-
-
-
- default xml namespace = Zotero.Integration.ns; with({});
- this.responseHeader = this.responseXML.env::Header;
- this.responseBody = this.responseXML.env::Body;
-
- this.needPrefs = this.body.setDocPrefs.length();
-
- try {
- this.initializeSession();
- if(this.needPrefs) {
- this.setDocPrefs();
- }
- if(this.body.reselectItem.length()) {
- this.reselectItem();
- } else {
- // if no more reselections, clear the reselectItem map
- this._session.reselectItem = new Object();
- }
- if(this.body.updateCitations.length() || this.body.updateBibliography.length()) {
- this.processCitations();
- }
-
- this.status = 200;
- this.statusText = "OK";
- } catch(e) {
- Zotero.debug(e);
- Components.utils.reportError(e);
-
- // Get a code for this error
- var code = (e.name ? e.name : "GenericError");
- var text = e.toString();
+ this.execCommand = function execCommand(agent, command) {
+ 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);
+ var integration = new Zotero.Integration.Document(application);
try {
- var text = Zotero.getString("integration.error."+e, Zotero.version);
- code = e;
- } catch(e) {}
-
- this.responseXML =
-
-
-
- XML-ENV:Sender
- z:{code}
-
-
-
- {text}
-
-
-
-
- this.status = 500;
- this.statusText = "Internal Server Error";
+ integration[command]();
+ } catch(e) {
+ integration._doc.displayAlert(Zotero.getString("integration.error.generic"),
+ Components.interfaces.zoteroIntegrationDocument.DIALOG_ICON_STOP,
+ Components.interfaces.zoteroIntegrationDocument.DIALOG_BUTTONS_OK);
+ throw e;
+ } finally {
+ integration.cleanup();
+ }
}
- // Zap chars that we don't want in our output
- this.responseText = this.responseXML.toXMLString().replace(/[\x00-\x08\x0B\x0C\x0E-\x1F]/g, '');
- Zotero.debug("Integration: SOAP Response\n"+this.responseText);
+ /**
+ * 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);
+ proc.run(false, ['-e', 'tell application "Firefox" to activate'], 2);
+ }
+ }
+ }
}
/**
- * Gets session data to associate with a request
- **/
-Zotero.Integration.Request.prototype.initializeSession = function() {
- default xml namespace = Zotero.Integration.ns; with({});
-
- if(this.header.client.@api != API_VERSION) {
- throw "incompatibleVersion";
- }
-
- var styleID = this.header.style.@id.toString();
- this._sessionID = this.header.session.@id.toString();
- if(this._sessionID === "" || !Zotero.Integration.sessions[this._sessionID]) {
- this._sessionID = Zotero.randomString();
- this._session = Zotero.Integration.sessions[this._sessionID] = new Zotero.Integration.Session();
-
- var preferences = {};
- for each(var pref in this.header.prefs.pref) {
- preferences[pref.@name] = pref.@value.toString();
+ * 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.name;
+}
+
+
+// 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) {
+ var dataString = this._doc.getDocumentData();
+ Zotero.debug(dataString);
+ if(!dataString) {
+ if(require) {
+ this._doc.displayAlert(Zotero.getString("integration.error.mustInsertCitation"),
+ Components.interfaces.zoteroIntegrationDocument.DIALOG_ICON_STOP,
+ Components.interfaces.zoteroIntegrationDocument.DIALOG_BUTTONS_OK);
+ } else {
+ // Set doc prefs if no data string yet
+ this._session = this._createNewSession(new Zotero.Integration.DocumentData());
+ if(dontRunSetDocPrefs) return false;
+
+ var ret = this._session.setDocPrefs(this._app.primaryFieldType, this._app.secondaryFieldType);
+ if(!ret) return false;
+ // save doc prefs in doc
+ this._doc.setDocumentData(this._session.data.serializeXML());
}
-
- this.needPrefs = this.needPrefs || !this._session.setStyle(styleID, preferences);
} else {
- this._session = Zotero.Integration.sessions[this._sessionID];
+ var data = new Zotero.Integration.DocumentData(dataString);
+ if(Zotero.Integration.sessions[data.sessionID]) {
+ this._session = Zotero.Integration.sessions[data.sessionID];
+ } else {
+ this._session = this._createNewSession(data);
+
+ // make sure style is defined
+ if(!this._session.style) {
+ this._session.setDocPrefs(this._app.primaryFieldType, this._app.secondaryFieldType);
+ }
+ this._doc.setDocumentData(this._session.data.serializeXML());
+ }
}
- this.responseHeader.appendChild();
+ this._session.resetRequest();
+ return true;
+}
+
+/**
+ * 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, onlyCheck) {
+ if(this._fields) return true;
+ if(!this._session && !this._getSession(require, true)) return false;
+
+ var fields = this._doc.getFields(this._session.data.prefs['fieldType']);
+ this._fields = [];
+ while(fields.hasMoreElements()) {
+ this._fields.push(fields.getNext().QueryInterface(Components.interfaces.zoteroIntegrationField));
+ }
+
+ if(require && !this._fields.length) {
+ this._doc.displayAlert(Zotero.getString("integration.error.mustInsertCitation"),
+ Components.interfaces.zoteroIntegrationDocument.DIALOG_ICON_STOP,
+ Components.interfaces.zoteroIntegrationDocument.DIALOG_BUTTONS_OK);
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * 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'])) {
+ this._doc.displayAlert(Zotero.getString("integration.error.cannotInsertHere"),
+ Components.interfaces.zoteroIntegrationDocument.DIALOG_ICON_STOP,
+ Components.interfaces.zoteroIntegrationDocument.DIALOG_BUTTONS_OK)
+ 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(editField) {
+ var deleteKeys = {};
+ this._deleteFields = [];
+ this._removeCodeFields = [];
+ this._bibliographyFields = [];
+ var bibliographyData = "";
+
+ // first collect entire bibliography
+ this._getFields();
+ var editFieldIndex = false;
+ 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) {
+ try {
+ this._session.addCitation(i, 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.toString());
+ }
+ msg += '\n\n'+Zotero.getString('integration.missingItem.description');
+ field.select();
+ var result = this._doc.displayAlert(msg, 1, 3);
+ if(result == 0) { // Cancel
+ throw "Integration update canceled by user";
+ } 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, fieldCode.substr(ITEM_CODE.length+1));
+ this._doc.activate();
+ }
+ }
+ } else {
+ throw e;
+ }
+ }
+ } else if(fieldCode.substr(0, BIBLIOGRAPHY_CODE.length) == BIBLIOGRAPHY_CODE) {
+ this._bibliographyFields.push(field);
+ if(!this._session.bibliographyData && !bibliographyData) {
+ bibliographyData = field.getCode().substr(BIBLIOGRAPHY_CODE.length+1);
+ }
+ }
+ }
+ }
+
+ // load uncited items from bibliography
+ if(bibliographyData && !this._session.bibliographyData) {
+ this._session.loadBibliographyData(bibliographyData);
+ }
+
+ this._session.updateItemSet();
+
+ // create new citation or edit existing citation
+ if(editFieldIndex) {
+ this._session.updateCitations(editFieldIndex-1);
+ var editFieldCode = editField.getCode().substr(ITEM_CODE.length+1);
+ var editCitation = editFieldCode ? this._session.unserializeCitation(editFieldCode, editFieldIndex) : null;
+
+ Zotero.Integration.activate();
+ var added = this._session.editCitation(editFieldIndex, editCitation);
+ this._doc.activate();
+
+ if(!added) {
+ if(editFieldCode) { // cancelled editing; just add as if nothing happened
+ this._session.addCitation(editFieldIndex, 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 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) {
+ field.setText(bibliographyText, true);
+ }
+ }
+
+ // update citations
+ this._session.updateUpdateIndices(forceCitations);
+ for(var i in this._session.updateIndices) {
+ citation = this._session.citationsByIndex[i];
+ if(!citation) continue;
+
+ if(citation.properties["delete"]) {
+ // delete citation
+ this._deleteFields.push(i);
+ } else if(!this.haveMissing) {
+ 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;
+ // XML uses real RTF, rather than the format used for
+ // integration, so we have to escape things properly
+ citationText = citationText.replace(/[\x7F-\uFFFF]/g,
+ Zotero.Integration.Session._rtfEscapeFunction).
+ replace("\t", "\\tab ", "g");
+ } else {
+ var citationText = this._session.style.formatCitation(citation, "RTF");
+ }
+
+ 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
+ for(var i=(this._deleteFields.length-1); i>=0; i--) {
+ this._fields[this._deleteFields[i]].delete();
+ }
+ 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() {
+ if(!this._getSession()) return;
+
+ var field = this._addField(true);
+ if(!field) return;
+
+ this._updateSession(field);
+ this._updateDocument();
}
/**
- * Sets preferences
- **/
-Zotero.Integration.Request.prototype.setDocPrefs = function() {
- default xml namespace = Zotero.Integration.ns; with({});
+ * Edits the citation at the cursor position.
+ */
+Zotero.Integration.Document.prototype.editCitation = function() {
+ if(!this._getSession(true)) return;
+ var field = this._doc.cursorInField(this._session.data.prefs['fieldType'])
+ if(!field) {
+ this._doc.displayAlert(Zotero.getString("integration.error.notInCitation"),
+ Components.interfaces.zoteroIntegrationDocument.DIALOG_ICON_STOP,
+ Components.interfaces.zoteroIntegrationDocument.DIALOG_BUTTONS_OK);
+ return;
+ }
+
+ this._updateSession(field);
+ this._updateDocument(false, false);
+}
+
+/**
+ * Adds a bibliography to the current document.
+ */
+Zotero.Integration.Document.prototype.addBibliography = function() {
+ if(!this._getSession(true)) return;
+
+ // Make sure we can have a bibliography
+ if(!this._session.style.hasBibliography) {
+ this._doc.displayAlert(Zotero.getString("integration.error.noBibliography"),
+ Components.interfaces.zoteroIntegrationDocument.DIALOG_ICON_STOP,
+ Components.interfaces.zoteroIntegrationDocument.DIALOG_BUTTONS_OK);
+ return;
+ }
+
+ // Make sure we have some citations
+ if(!this._getFields(true)) return;
+
+ 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
+ if(!this._getFields(true)) return false;
+ 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) {
+ this._doc.displayAlert(Zotero.getString("integration.error.mustInsertBibliography"),
+ Components.interfaces.zoteroIntegrationDocument.DIALOG_ICON_STOP,
+ Components.interfaces.zoteroIntegrationDocument.DIALOG_BUTTONS_OK);
+ return;
+ }
+
+ 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() {
+ if(!this._getFields(true)) return false;
+
+ // Send request, forcing update of citations and bibliography
+ this._updateSession();
+ this._updateDocument(true, true);
+}
+
+/**
+ * Deletes field codes.
+ */
+Zotero.Integration.Document.prototype.removeCodes = function() {
+ if(!this._getFields(true)) return false;
+
+ 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() {
+ if(this._getSession(false, true)) this._getFields();
+ var oldData = this._session.setDocPrefs(this._app.primaryFieldType, this._app.secondaryFieldType);
+ 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.reselectedItems = 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 {
+ this.style = Zotero.Styles.get(data.style.styleID).csl;
+ this.dateModified = new Object();
+
+ this.itemSet = this.style.createItemSet();
+ this.loadUncitedItems();
+ } 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;
};
- io.openOffice = this.header.client.@agent == "OpenOffice";
-
- var oldStyle = io.style = this._session.styleID;
- io.useEndnotes = this._session.prefs.useEndnotes;
- io.useBookmarks = this._session.prefs.fieldType;
+ 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;
+ }
Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
.getService(Components.interfaces.nsIWindowWatcher)
.openWindow(null, 'chrome://zotero/content/integrationDocPrefs.xul', '',
'chrome,modal,centerscreen' + (Zotero.isWin ? ',popup' : ''), io, true);
- if(!oldStyle || oldStyle != io.style
- || io.useEndnotes != this._session.prefs.useEndnotes
- || io.useBookmarks != this._session.prefs.fieldType) {
- this._session.regenerateAll = this._session.bibliographyHasChanged = true;
-
- if(oldStyle != io.style) {
- this._session.setStyle(io.style, this._session.prefs);
- }
- }
- this._session.prefs.useEndnotes = io.useEndnotes;
- this._session.prefs.fieldType = io.useBookmarks;
+ if(!io.style) return false;
- this.responseHeader.appendChild(