references #698, Migration away from VBA

Adds a Python/py-appscript-based plug-in for Word 2008. To get this to work, you'll need to copy the Zotero directory (not its contents) to ~/Microsoft User Data/Word Script Menu Items and install py-appscript (sudo easy_install appscript)

Some caveats:
- Requires Word be installed at /Applications/Microsoft Office 2008/Microsoft Word 2008.app (this is fixable, but I'm still determining the best way to solve it)
- Still need to figure out what to do with items that have been deleted from the DB (right now, we just ignore them)
- Sometimes, Python.app launches with the script, which seems to slow execution time
This commit is contained in:
Simon Kornblith 2008-07-10 11:05:43 +00:00
parent cf93bd7042
commit 53da1ccb03
4 changed files with 342 additions and 72 deletions

View file

@ -638,7 +638,7 @@ Zotero.CSL.prototype.formatBibliography = function(itemSet, format) {
} else {
if(format == "RTF" || format == "Integration") {
if(format == "RTF") {
preamble = "{\\rtf\\ansi{\\fonttbl\\f0\\froman Times New Roman;}{\\colortbl;\\red255\\green255\\blue255;}\\pard\\f0";
preamble = "{\\rtf\\ansi{\\fonttbl\\f0\\froman Times New Roman;}{\\colortbl;\\red255\\green255\\blue255;}\\pard\\f0\r\n";
}
var tabStop = null;
@ -774,7 +774,7 @@ Zotero.CSL.prototype.formatBibliography = function(itemSet, format) {
output = output.substr(0, output.length-returnChars.length);
// add bracket for RTF
if(format == "RTF") output += "}";
if(format == "RTF") output += "\\par }";
}
return preamble+output;

View file

@ -15,19 +15,23 @@
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.
limitations under the License.s
***** END LICENSE BLOCK *****
*/
const API_VERSION = 5;
const API_VERSION = 1;
const COMPAT_API_VERSION = 5;
Zotero.Integration = new function() {
var _contentLengthRe = /[\r\n]Content-Length: *([0-9]+)/i;
var _XMLRe = /<\?[^>]+\?>/;
var _onlineObserverRegistered;
this.ns = "http://www.zotero.org/namespaces/SOAP";
this.sessions = {};
var ns = "http://www.zotero.org/namespaces/SOAP";
this.ns = ns;
this.init = init;
this.handleHeader = handleHeader;
@ -37,6 +41,8 @@ Zotero.Integration = new function() {
* initializes a very rudimentary web server used for SOAP RPC
*/
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()
@ -89,15 +95,16 @@ Zotero.Integration = new function() {
function handleEnvelope(envelope) {
Zotero.debug("Integration: SOAP Request\n"+envelope);
envelope = envelope.replace(_XMLRe, "");
var env = this.env;
var env = new Namespace("http://schemas.xmlsoap.org/soap/envelope/");
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 {
} else if(!xml.env::Header.children().length()) {
// old style SOAP request
var name = request.localName();
if(Zotero.Integration.SOAP[name]) {
if(Zotero.Integration.SOAP_Compat[name]) {
if(request.input.length()) {
// split apart passed parameters (same colon-escaped format
// as we pass)
@ -126,7 +133,7 @@ Zotero.Integration = new function() {
}
// execute request
var output = Zotero.Integration.SOAP[name](vars);
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
@ -161,6 +168,11 @@ Zotero.Integration = new function() {
} else {
Zotero.debug("Integration: SOAP method not supported");
}
} else {
// execute request
request = new Zotero.Integration.Request(xml);
return _generateResponse(request.status+" "+request.statusText,
'text/xml; charset="UTF-8"', request.responseText);
}
}
@ -381,30 +393,223 @@ Zotero.Integration.DataListener.prototype._requestFinished = function(response)
}
}
Zotero.Integration.SOAP = new function() {
Zotero.Integration.Request = function(xml) {
default xml namespace = Zotero.Integration.ns; with({});
var env = Zotero.Integration.env;
this.header = xml.env::Header;
this.body = xml.env::Body;
this.responseXML = <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:z={Zotero.Integration.ns}>
<SOAP-ENV:Header/>
<SOAP-ENV:Body/>
</SOAP-ENV:Envelope>
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.updateCitations.length() || this.body.updateBibliography.length()) {
this.processCitations();
}
this.status = 200;
this.statusText = "OK";
} catch(e) {
Components.utils.reportError(e);
Zotero.debug(e);
// Get a code for this error
var code = (e.name ? e.name : "GenericError");
var text = e.toString();
try {
var text = Zotero.getString("integration.error."+e, Zotero.version);
code = e;
} catch(e) {
}
this.responseXML = <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:z={Zotero.Integration.ns}>
<SOAP-ENV:Body>
<SOAP-ENV:Fault>
<SOAP-ENV:Code>
<SOAP-ENV:Value>XML-ENV:Sender</SOAP-ENV:Value>
<SOAP-ENV:Subcode>z:{code}</SOAP-ENV:Subcode>
</SOAP-ENV:Code>
</SOAP-ENV:Fault>
<SOAP-ENV:Reason>
<SOAP-ENV:Text>{text}</SOAP-ENV:Text>
</SOAP-ENV:Reason>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
this.status = 500;
this.statusText = "Internal Server Error";
}
// 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);
}
/**
* 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();
}
this.needPrefs = this.needPrefs || !this._session.setStyle(styleID, preferences);
if(this.header.bibliography.length()) {
session.loadBibliographyData(Zotero.Utilities.prototype.trim(this.header.bibliography.toString()));
}
} else {
this._session = Zotero.Integration.sessions[this._sessionID];
}
this.responseHeader.appendChild(<session id={this._sessionID}/>);
}
/**
* Sets preferences
**/
Zotero.Integration.Request.prototype.setDocPrefs = function() {
default xml namespace = Zotero.Integration.ns; with({});
var io = new function() {
this.wrappedJSObject = this;
}
io.openOffice = this.header.client.@agent == "OpenOffice.org"
var oldStyle = io.style = this._session.styleID;
io.useEndnotes = this._session.prefs.useEndnotes;
io.useBookmarks = this._session.prefs.fieldType;
this.watcher = 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);
this._session.prefs.useEndnotes = io.useEndnotes;
this._session.prefs.fieldType = io.useBookmarks;
Zotero.debug("prefs are")
Zotero.debug(this._session.prefs);
if(!oldStyle || oldStyle != io.style
|| io.useEndnotes != this._session.prefs.useEndnotes
|| io.useBookmarks != this._session.prefs.fieldType) {
this._session.regenerateAll = this._session.bibliographyHasChanged = true;
this._session.setStyle(io.style, this._session.prefs);
}
this.responseHeader.appendChild(<style
id={io.style} class={this._session.style.class}
hasBibliography={this._session.style.hasBibliography}/>);
this.responseHeader.appendChild(<prefs>
<pref name="useEndnotes" value={io.useEndnotes}/>
<pref name="fieldType" value={io.useBookmarks}/>
</prefs>);
this.responseBody.appendChild(<setDocPrefsResponse/>);
}
/**
* Updates citations
**/
Zotero.Integration.Request.prototype.processCitations = function() {
default xml namespace = Zotero.Integration.ns; with({});
// get whether to edit bibliography or edit a citation
var editCitationIndex = this.body.updateCitations.@edit.toString();
// first collect entire bibliography
var editCitation = false;
for each(var citation in this.header.citations.citation) {
// trim spacing characters
var citationData = Zotero.Utilities.prototype.trim(citation.toString());
if(citation.@index.toString() === editCitationIndex) {
if(!citation.@new.toString()) { // new citation
// save citation data
editCitation = this._session.unserializeCitation(citationData, citation.@index.toString());
}
} else {
this._session.addCitation(citation.@index.toString(), citationData);
}
}
this._session.updateItemSet();
if(editCitationIndex) {
this._session.updateCitations(editCitationIndex-1);
var added = this._session.editCitation(editCitationIndex, editCitation);
if(!added) {
if(editCitation) {
this._session.addCitation(editCitationIndex, editCitation);
} else {
this._session.deleteCitation(editCitationIndex);
}
}
this._session.updateItemSet();
}
this._session.updateCitations();
if(this.body.updateBibliography.@edit.toString()) {
this._session.editBibliography();
}
// update
var output = new Array();
if(this.body.updateBibliography.length() // if we want updated bib
&& (this._session.bibliographyHasChanged // and bibliography changed
|| this.body.updateBibliography.@force.toString())) { // or if we should generate regardless of changes
if(this._session.bibliographyDataHasChanged) {
this.responseBody.updateBibliographyResponse.code = this._session.getBibliographyData();
}
this.responseBody.updateBibliographyResponse.text = this._session.getBibliography(true);
}
// get citations
if(this.body.updateCitations.length()) {
this.responseBody.updateCitationsResponse.citations = this._session.getCitations(!!this.body.updateCitations.@force.toString() || this._session.regenerateAll, true);
}
// reset citationSet
this._session.resetRequest();
}
Zotero.Integration.SOAP_Compat = new function() {
// SOAP methods
this.init = init;
this.update = update;
this.restoreSession = restoreSession;
this.setDocPrefs = setDocPrefs;
var _sessions = new Array();
var watcher;
function init() {
watcher = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
.getService(Components.interfaces.nsIWindowWatcher);
}
/*
* generates a new citation for a given item
* ACCEPTS: sessionID, bibliographyMode, citationMode, editCitationIndex(, fieldIndex, fieldName)+
* RETURNS: bibliography, documentData(, fieldIndex, fieldRename, fieldContent)+
*/
function update(vars) {
if(!_sessions[vars[0]]) return "ERROR:sessionExpired";
if(!Zotero.Integration.sessions[vars[0]]) return "ERROR:sessionExpired";
var session = _sessions[vars[0]];
var session = Zotero.Integration.sessions[vars[0]];
var bibliographyMode = vars[1];
var citationMode = vars[2];
@ -468,8 +673,9 @@ Zotero.Integration.SOAP = new function() {
output.push("!");
}
if(session.documentDataHasChanged) {
output.push(session.getDocumentData());
if(session.bibliographyDataHasChanged) {
var data = session.getBibliographyData();
output.push(data !== "" ? data : "X");
} else {
output.push("!");
}
@ -490,7 +696,7 @@ Zotero.Integration.SOAP = new function() {
*/
function restoreSession(vars) {
if(!vars || !_checkVersion(vars[0])) {
return "ERROR:"+Zotero.getString("integration.incompatibleVersion", Zotero.version);
return "ERROR:"+Zotero.getString("integration.error.incompatibleVersion", Zotero.version);
}
try {
@ -500,8 +706,8 @@ Zotero.Integration.SOAP = new function() {
}
var sessionID = Zotero.randomString();
var session = _sessions[sessionID] = new Zotero.Integration.Session();
session.setStyle(vars[2], vars[3], vars[4]);
var session = Zotero.Integration.sessions[sessionID] = new Zotero.Integration.Session();
session.setStyle(vars[2], {useEndnotes:vars[3], fieldType:vars[4]});
var encounteredItem = new Object();
var newField = new Object();
@ -512,7 +718,7 @@ Zotero.Integration.SOAP = new function() {
}
session.updateItemSet(session.citationsByItemID);
if(vars[1] != "!") session.loadDocumentData(vars[1]);
if(vars[1] != "!") session.loadBibliographyData(vars[1]);
session.sortItemSet();
session.resetRequest();
@ -526,7 +732,7 @@ Zotero.Integration.SOAP = new function() {
*/
function setDocPrefs(vars) {
if(!vars || !vars.length || !_checkVersion(vars[1])) {
return "ERROR:"+Zotero.getString("integration.incompatibleVersion", Zotero.version);
return "ERROR:"+Zotero.getString("integration.error.incompatibleVersion", Zotero.version);
}
var io = new function() {
@ -542,22 +748,26 @@ Zotero.Integration.SOAP = new function() {
if(vars[0] == "!") {
// no session ID; generate a new one
var sessionID = Zotero.randomString();
var session = _sessions[sessionID] = new Zotero.Integration.Session();
var session = Zotero.Integration.sessions[sessionID] = new Zotero.Integration.Session();
} else {
// session ID exists
var sessionID = vars[0];
var session = _sessions[sessionID];
var session = Zotero.Integration.sessions[sessionID];
if(!session) return "ERROR:sessionExpired";
oldStyle = io.style = session.styleID;
io.useEndnotes = session.useEndnotes;
io.useBookmarks = session.useBookmarks;
io.useEndnotes = session.prefs.useEndnotes;
io.useBookmarks = session.prefs.fieldType;
}
watcher.openWindow(null, 'chrome://zotero/content/integrationDocPrefs.xul', '',
'chrome,modal,centerscreen' + (Zotero.isWin ? ',popup' : ''), io, true);
session.setStyle(io.style, io.useEndnotes, io.useBookmarks);
if(!oldStyle || oldStyle == io.style) {
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);
session.prefs.useEndnotes = io.useEndnotes;
session.prefs.fieldType = io.useBookmarks;
session.setStyle(io.style, session.prefs);
if(!oldStyle || oldStyle != io.style) {
session.regenerateAll = session.bibliographyHasChanged = true;
}
@ -571,7 +781,7 @@ Zotero.Integration.SOAP = new function() {
function _checkVersion(version) {
versionParts = version.split("/");
Zotero.debug("Integration: client version "+version);
if(versionParts.length != 3 || versionParts[1] != API_VERSION) return false;
if(versionParts.length != 3 || versionParts[1] != COMPAT_API_VERSION) return false;
return true;
}
}
@ -582,6 +792,7 @@ Zotero.Integration.SOAP = new function() {
Zotero.Integration.Session = function() {
// holds items not in document that should be in bibliography
this.uncitedItems = new Object();
this.prefs = new Object();
this.resetRequest();
}
@ -589,15 +800,25 @@ Zotero.Integration.Session = function() {
/*
* changes the Session style
*/
Zotero.Integration.Session.prototype.setStyle = function(styleID, useEndnotes, useBookmarks) {
this.styleID = styleID;
this.style = Zotero.Cite.getStyle(styleID);
this.useEndnotes = useEndnotes;
this.useBookmarks = useBookmarks;
this.itemSet = this.style.createItemSet();
this.dateModified = new Object();
this.loadUncitedItems();
Zotero.Integration.Session.prototype.setStyle = function(styleID, prefs) {
this.prefs = prefs;
if(styleID) {
this.styleID = styleID;
try {
this.style = Zotero.Cite.getStyle(styleID);
this.dateModified = new Object();
this.itemSet = this.style.createItemSet();
this.loadUncitedItems();
} catch(e) {
Zotero.debug(e)
this.styleID = undefined;
return false;
}
return true;
}
return false;
}
/*
@ -609,7 +830,7 @@ Zotero.Integration.Session.prototype.resetRequest = function() {
this.regenerateAll = false;
this.bibliographyHasChanged = false;
this.documentDataHasChanged = false;
this.bibliographyDataHasChanged = false;
this.updateItemIDs = new Object();
this.updateIndices = new Object()
}
@ -829,6 +1050,7 @@ Zotero.Integration.Session.prototype.previewCitation = function(citation) {
}
// get preview citation
var text = this.style.formatCitation(citation, "Integration");
// delete from item set
if(deleteItems.length) {
this.itemSet.remove(deleteItems);
@ -1011,7 +1233,7 @@ Zotero.Integration.Session.prototype.editBibliography = function() {
var bibliographyEditor = new Zotero.Integration.Session.BibliographyEditInterface(this);
var io = new function() { this.wrappedJSObject = bibliographyEditor; }
this.documentDataHasChanged = this.bibliographyHasChanged = true;
this.bibliographyDataHasChanged = this.bibliographyHasChanged = true;
Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
.getService(Components.interfaces.nsIWindowWatcher)
@ -1023,14 +1245,26 @@ Zotero.Integration.Session.prototype.editBibliography = function() {
/*
* gets integration bibliography
*/
Zotero.Integration.Session.prototype.getBibliography = function() {
return this.style.formatBibliography(this.itemSet, "Integration");
Zotero.Integration.Session.prototype.getBibliography = function(useXML) {
// get preview citation
if(useXML) {
// use real RTF in XML incarnation, but chop off the first \n
var text = this.style.formatBibliography(this.itemSet, "RTF")
var nlIndex = text.indexOf("\n");
if(nlIndex !== -1) {
return "{\\rtf "+text.substr(text.indexOf("\n"));
} else {
return "";
}
} else {
return this.style.formatBibliography(this.itemSet, "Integration");
}
}
/*
* gets citations in need of update
*/
Zotero.Integration.Session.prototype.getCitations = function(regenerateAll) {
Zotero.Integration.Session.prototype.getCitations = function(regenerateAll, useXML) {
if(regenerateAll || this.regenerateAll) {
// update all indices
for(var i=0; i<this.citationsByIndex.length; i++) {
@ -1047,39 +1281,75 @@ Zotero.Integration.Session.prototype.getCitations = function(regenerateAll) {
}
}
var output = [];
var output = (useXML ? <citations/> : []);
var citation;
for(var i in this.updateIndices) {
citation = this.citationsByIndex[i];
if(!citation) continue;
output.push(i);
if(citation.properties.delete) {
if(useXML) {
var citationXML = <citation index={i}/>;
} else {
output.push(i);
}
if(citation.properties["delete"]) {
// delete citation
output.push("!");
output.push("!");
if(useXML) {
citationXML.@delete = "1";
} else {
output.push("!");
output.push("!");
}
} else {
var field = this.getCitationField(citation);
output.push(field == citation.properties.field ? "!" : field);
if(useXML) {
if(field != citation.properties.field) {
citationXML.code = field;
}
} else {
output.push(field == citation.properties.field ? "!" : field);
}
if(citation.properties.custom) {
output.push(citation.properties.custom);
var citationText = citation.properties.custom;
if(useXML) {
// 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 if(useXML) {
var citationText = this.style.formatCitation(citation, "RTF");
} else {
var citation = this.style.formatCitation(citation, "Integration");
if(citation == "") citation = " ";
output.push(citation);
var citationText = this.style.formatCitation(citation, "Integration");
}
if(useXML) {
citationXML.text = "{\\rtf "+citationText+"}";
} else {
output.push(citationText == "" ? " " : citationText);
}
}
if(useXML) output.appendChild(citationXML);
}
return output;
}
Zotero.Integration.Session._rtfEscapeFunction = function(aChar) {
return "{\\uc0\\u"+aChar.charCodeAt(0).toString()+"}"
}
/*
* loads document data from a JSON object
*/
Zotero.Integration.Session.prototype.loadDocumentData = function(json) {
Zotero.Integration.Session.prototype.loadBibliographyData = function(json) {
var documentData = Zotero.JSON.unserialize(json);
// set uncited
@ -1119,12 +1389,12 @@ Zotero.Integration.Session.prototype.loadUncitedItems = function() {
/*
* saves document data from a JSON object
*/
Zotero.Integration.Session.prototype.getDocumentData = function() {
var documentData = {};
Zotero.Integration.Session.prototype.getBibliographyData = function() {
var bibliographyData = {};
// add uncited if there is anything
for(var item in this.uncitedItems) {
documentData.uncited = this.uncitedItems;
bibliographyData.uncited = this.uncitedItems;
break;
}
@ -1135,16 +1405,16 @@ Zotero.Integration.Session.prototype.getDocumentData = function() {
if(custom !== "") {
var itemID = this.itemSet.items[i].getID();
if(!documentData.custom) documentData.custom = {};
documentData.custom[itemID] = custom;
if(!bibliographyData.custom) bibliographyData.custom = {};
bibliographyData.custom[itemID] = custom;
}
}
}
if(documentData.uncited || documentData.custom) {
return Zotero.JSON.serialize(documentData);
if(bibliographyData.uncited || bibliographyData.custom) {
return Zotero.JSON.serialize(bibliographyData);
} else {
return "X"; // nothing
return ""; // nothing
}
}

View file

@ -258,7 +258,6 @@ var Zotero = new function(){
Zotero.Schema.updateScrapersRemote();
// Initialize integration web server
Zotero.Integration.SOAP.init();
Zotero.Integration.init();
// Initialize data web server

View file

@ -477,7 +477,8 @@ annotations.collapse.tooltip = Collapse Annotation
annotations.expand.tooltip = Expand Annotation
annotations.oneWindowWarning = Annotations for a snapshot may only be opened in one browser window simultaneously. This snapshot will be opened without annotations.
integration.incompatibleVersion = This version of the Zotero Word plugin ($INTEGRATION_VERSION) is incompatible with the currently installed version of the Zotero Firefox extension (%1$S). Please ensure you are using the latest versions of both components.
integration.error.incompatibleVersion = This version of the Zotero Word plugin ($INTEGRATION_VERSION) is incompatible with the currently installed version of the Zotero Firefox extension (%1$S). Please ensure you are using the latest versions of both components.
integration.fields.label = Fields
integration.referenceMarks.label = ReferenceMarks
integration.fields.caption = Microsoft Word Fields are less likely to be accidentally modified, but cannot be shared with OpenOffice.org.