![Simon Kornblith](/assets/img/avatar_default.png)
closes #238, present dialog if no import translator is available for a file closes #240, change XML namespaces
383 lines
No EOL
10 KiB
JavaScript
383 lines
No EOL
10 KiB
JavaScript
Scholar.Integration = new function() {
|
|
var _contentLengthRe = /[\r\n]Content-Length: *([0-9]+)/i;
|
|
var _XMLRe = /<\?[^>]+\?>/;
|
|
this.ns = "http://www.zotero.org/namespaces/SOAP";
|
|
|
|
this.init = init;
|
|
this.handleHeader = handleHeader;
|
|
this.handleEnvelope = handleEnvelope;
|
|
|
|
/*
|
|
* initializes a very rudimentary web server used for SOAP RPC
|
|
*/
|
|
function init() {
|
|
// start listening on socket
|
|
var sock = Components.classes["@mozilla.org/network/server-socket;1"];
|
|
serv = sock.createInstance();
|
|
serv = serv.QueryInterface(Components.interfaces.nsIServerSocket);
|
|
|
|
// bind to a random port on loopback only
|
|
serv.init(50001, true, -1);
|
|
serv.asyncListen(Scholar.Integration.SocketListener);
|
|
|
|
Scholar.debug("Integration HTTP server listening on 127.0.0.1:"+serv.port);
|
|
}
|
|
|
|
/*
|
|
* 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) {
|
|
Scholar.debug("Integration: got SOAP envelope");
|
|
Scholar.debug(envelope);
|
|
envelope = envelope.replace(_XMLRe, "");
|
|
|
|
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) {
|
|
Scholar.debug("Integration: SOAP method not supported: invalid namespace");
|
|
} else {
|
|
var name = request.localName();
|
|
if(Scholar.Integration.SOAP[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;
|
|
|
|
colonIndex = input.indexOf(":");
|
|
while(colonIndex != -1) {
|
|
if(input[colonIndex+1] == ":") { // escaped
|
|
vars[i] += input.substr(0, colonIndex+1);
|
|
input = input.substr(colonIndex+2);
|
|
} else { // not escaped
|
|
vars[i] += input.substr(0, colonIndex);
|
|
i++;
|
|
vars[i] = "";
|
|
input = input.substr(colonIndex+1);
|
|
}
|
|
colonIndex = input.indexOf(":");
|
|
}
|
|
vars[i] += input;
|
|
} else {
|
|
var vars = null;
|
|
}
|
|
|
|
// execute request
|
|
var output = Scholar.Integration.SOAP[name](vars);
|
|
|
|
// ugh: we can't use real SOAP, since AppleScript VBA can't pass
|
|
// objects, so implode arrays
|
|
if(!output) {
|
|
output = "";
|
|
}
|
|
|
|
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 = <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
|
|
<SOAP-ENV:Body>
|
|
<m:{name}Response xmlns:m={this.ns}>
|
|
<output>{output}</output>
|
|
</m:{name}Response>
|
|
</SOAP-ENV:Body>
|
|
</SOAP-ENV:Envelope>;
|
|
|
|
// return OK
|
|
return _generateResponse("200 OK", 'text/xml; charset="utf-8"',
|
|
responseEnvelope.toXMLString());
|
|
} else {
|
|
Scholar.debug("Integration: SOAP method not supported");
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* generates the response to an HTTP request
|
|
*/
|
|
function _generateResponse(status, contentType, body) {
|
|
var response = "HTTP/1.0 "+status+"\r\n";
|
|
|
|
if(body) {
|
|
if(contentType) {
|
|
response += "Content-Type: "+contentType+"\r\n";
|
|
}
|
|
response += "Content-Length: "+body.length+"\r\n\r\n"+body;
|
|
} else {
|
|
response += "Content-Length: 0\r\n\r\n"
|
|
}
|
|
|
|
return response;
|
|
}
|
|
}
|
|
|
|
Scholar.Integration.SocketListener = new function() {
|
|
this.onSocketAccepted = onSocketAccepted;
|
|
|
|
/*
|
|
* 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(0, 0, 0);
|
|
|
|
var dataListener = new Scholar.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);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* handles the actual acquisition of data
|
|
*/
|
|
Scholar.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)
|
|
*/
|
|
Scholar.Integration.DataListener.prototype.onStartRequest = function(request, context) {}
|
|
|
|
/*
|
|
* called when a request stops
|
|
*/
|
|
Scholar.Integration.DataListener.prototype.onStopRequest = function(request, context, status) {
|
|
this.iStream.close();
|
|
this.oStream.close();
|
|
}
|
|
|
|
/*
|
|
* called when new data is available
|
|
*/
|
|
Scholar.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
|
|
*/
|
|
Scholar.Integration.DataListener.prototype._headerFinished = function() {
|
|
this.headerFinished = true;
|
|
var output = Scholar.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
|
|
*/
|
|
Scholar.Integration.DataListener.prototype._bodyData = function() {
|
|
if(this.body.length >= this.bodyLength) {
|
|
if(this.body.length > this.bodyLength) {
|
|
// truncate
|
|
this.body = this.body.substr(0, this.bodyLength);
|
|
}
|
|
|
|
var output = Scholar.Integration.handleEnvelope(this.body);
|
|
this._requestFinished(output);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* returns HTTP data from a request
|
|
*/
|
|
Scholar.Integration.DataListener.prototype._requestFinished = function(response) {
|
|
// close input stream
|
|
this.iStream.close();
|
|
|
|
// write response
|
|
this.oStream.write(response, response.length);
|
|
|
|
// close output stream
|
|
this.oStream.close();
|
|
}
|
|
|
|
Scholar.Integration.SOAP = new function() {
|
|
this.init = init;
|
|
this.getCitation = getCitation;
|
|
this.getBibliography = getBibliography;
|
|
this.setDocPrefs = setDocPrefs;
|
|
|
|
var window;
|
|
|
|
function init() {
|
|
window = Components.classes["@mozilla.org/appshell/appShellService;1"]
|
|
.getService(Components.interfaces.nsIAppShellService)
|
|
.hiddenDOMWindow;
|
|
}
|
|
|
|
/*
|
|
* generates a new citation for a given item
|
|
* ACCEPTS: style[, itemString, newItemIndex]
|
|
* RETURNS: (newItem, citation)
|
|
*/
|
|
function getCitation(vars) {
|
|
// get items
|
|
var io = {dataIn: null, dataOut: null};
|
|
window.openDialog('chrome://scholar/content/selectItemsDialog.xul','',
|
|
'chrome,popup,modal,centerscreen',io);
|
|
|
|
if(io.dataOut) { // cancel was not pressed
|
|
var selectedItemIDs = io.dataOut;
|
|
var selectedItems = Scholar.Items.get(selectedItemIDs);
|
|
|
|
var style = vars[0];
|
|
if(vars[1]) { // some items already exist in the document
|
|
var itemString = vars[1]; // underscore-delimited string
|
|
|
|
var newItemIndex = parseInt(vars[2]); // index at which the
|
|
// item belongs in
|
|
// itemString
|
|
|
|
// splice in the new item ID
|
|
if(newItemIndex == -1) { // at beginning
|
|
var items = selectedItems.concat(Scholar.Items.get(itemString.split("_")));
|
|
} else { // at newItemIndex
|
|
var items = Scholar.Items.get(itemString.substr(0, newItemIndex).split("_")).
|
|
concat(selectedItems);
|
|
|
|
if(newItemIndex != itemString.length) { // not at the end
|
|
items = items.concat(Scholar.Items.get(itemString.substr(newItemIndex+1).split("_")))
|
|
}
|
|
}
|
|
} else { // this is the first item and the only item to worry
|
|
// about
|
|
var items = selectedItems;
|
|
}
|
|
|
|
var citation = Scholar.Cite.getCitation(style, selectedItems, items, "Integration");
|
|
|
|
return [selectedItemIDs.join("_"), citation];
|
|
}
|
|
}
|
|
|
|
/*
|
|
* gets a bibliography
|
|
* ACCEPTS: style, itemString
|
|
* RETURNS: bibliography
|
|
*/
|
|
function getBibliography(vars) {
|
|
// get items
|
|
var itemIDs = vars[1].split("_");
|
|
var items = Scholar.Items.get(itemIDs);
|
|
|
|
return Scholar.Cite.getBibliography(vars[0], items, "Integration");
|
|
}
|
|
|
|
/*
|
|
* sets document preferences
|
|
* ACCEPTS: [currentStyle]
|
|
* RETURNS: (style, styleClass)
|
|
*/
|
|
function setDocPrefs(vars) {
|
|
var io = new Object();
|
|
if(vars && vars[0]) {
|
|
io.style = vars[0];
|
|
}
|
|
|
|
window.openDialog('chrome://scholar/content/integrationDocPrefs.xul','',
|
|
'chrome,popup,modal,centerscreen',io);
|
|
var styleClass = Scholar.Cite.getStyleClass(io.style);
|
|
return [io.style, styleClass];
|
|
}
|
|
} |