- implement Zotero.Styles.install, refine Zotero.Style.delete, and restore functionality to Styles prefpane
- allow deletion of multiple styles simultaneously - split Zotero.Styles/Zotero.Style and Zotero.CSL into style.js and csl.js respectively - add Zotero.File.getBinaryContents for binary-safe file reading - add Zotero.MIMETypeHandler to provide a unified interface for registering observers and capturing MIME types with Zotero
This commit is contained in:
parent
0769e0e1f8
commit
bf8e4eae28
11 changed files with 852 additions and 655 deletions
|
@ -1110,16 +1110,18 @@ function onOpenURLCustomized()
|
|||
|
||||
/**
|
||||
* Refreshes the list of styles in the styles pane
|
||||
**/
|
||||
* @param {String} cslID Style to select
|
||||
*/
|
||||
function refreshStylesList(cslID) {
|
||||
var treechildren = document.getElementById('styleManager-rows');
|
||||
while (treechildren.hasChildNodes()) {
|
||||
treechildren.removeChild(treechildren.firstChild);
|
||||
}
|
||||
|
||||
var styles = Zotero.Styles.getAll();
|
||||
var styles = Zotero.Styles.getVisible();
|
||||
|
||||
var selectIndex = false;
|
||||
var i = 0;
|
||||
for each(var style in styles) {
|
||||
var treeitem = document.createElement('treeitem');
|
||||
var treerow = document.createElement('treerow');
|
||||
|
@ -1138,8 +1140,7 @@ function refreshStylesList(cslID) {
|
|||
titleCell.setAttribute('label', style.title);
|
||||
updatedCell.setAttribute('label', updatedDate);
|
||||
// if not EN
|
||||
if (style.styleID.length < Zotero.ENConverter.uriPrefix.length ||
|
||||
style.styleID.substr(0, Zotero.ENConverter.uriPrefix.length) != Zotero.ENConverter.uriPrefix) {
|
||||
if(style.type == "csl") {
|
||||
cslCell.setAttribute('src', 'chrome://zotero/skin/tick.png');
|
||||
}
|
||||
|
||||
|
@ -1152,6 +1153,7 @@ function refreshStylesList(cslID) {
|
|||
if (cslID == style.styleID) {
|
||||
document.getElementById('styleManager').view.selection.select(i);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1169,70 +1171,52 @@ function addStyle() {
|
|||
|
||||
var rv = fp.show();
|
||||
if (rv == nsIFilePicker.returnOK || rv == nsIFilePicker.returnReplace) {
|
||||
var file = fp.file;
|
||||
|
||||
// read file
|
||||
var iStream = Components.classes["@mozilla.org/network/file-input-stream;1"]
|
||||
.createInstance(Components.interfaces.nsIFileInputStream);
|
||||
iStream.init(file, 0x01, 0664, 0);
|
||||
var bStream = Components.classes["@mozilla.org/binaryinputstream;1"]
|
||||
.createInstance(Components.interfaces.nsIBinaryInputStream);
|
||||
bStream.setInputStream(iStream);
|
||||
|
||||
var read = bStream.readBytes(6);
|
||||
|
||||
if(read == "\x00\x08\xFF\x00\x00\x00") {
|
||||
// EndNote style
|
||||
|
||||
// read the rest of the bytes in the file
|
||||
read += bStream.readBytes(file.fileSize-6);
|
||||
|
||||
// get name and modification date
|
||||
var name = file.leafName.replace(/\.ens$/i, "");
|
||||
var date = new Date(file.lastModifiedTime);
|
||||
|
||||
var cslID = Zotero.Styles.install(read, false, date, name);
|
||||
} else {
|
||||
// This _should_ get the right charset for us automatically
|
||||
var fileURI = Components.classes["@mozilla.org/network/protocol;1?name=file"]
|
||||
.getService(Components.interfaces.nsIFileProtocolHandler)
|
||||
.getURLSpecFromFile(file);
|
||||
var req = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"].
|
||||
createInstance();
|
||||
req.open("GET", fileURI, false);
|
||||
req.overrideMimeType("text/plain");
|
||||
try {
|
||||
req.send(null);
|
||||
} catch(e) {
|
||||
styleImportError();
|
||||
throw e;
|
||||
}
|
||||
|
||||
var cslID = Zotero.Styles.install(req.responseText);
|
||||
}
|
||||
Zotero.Styles.install(fp.file);
|
||||
}
|
||||
|
||||
if(cslID !== false) this.refreshStylesList(cslID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a style from the style pane
|
||||
* Deletes selected styles from the styles pane
|
||||
**/
|
||||
function deleteStyle() {
|
||||
// get selected cslIDs
|
||||
var tree = document.getElementById('styleManager');
|
||||
if(tree.currentIndex == -1) return;
|
||||
var treeitem = tree.lastChild.childNodes[tree.currentIndex];
|
||||
var cslID = treeitem.getAttribute('id').substr(11);
|
||||
var treeItems = tree.lastChild.childNodes;
|
||||
var cslIDs = [];
|
||||
var start = {};
|
||||
var end = {};
|
||||
var nRanges = tree.view.selection.getRangeCount();
|
||||
for(var i=0; i<nRanges; i++) {
|
||||
tree.view.selection.getRangeAt(i, start, end);
|
||||
for(var j=start.value; j<=end.value; j++) {
|
||||
cslIDs.push(treeItems[j].getAttribute('id').substr(11));
|
||||
}
|
||||
}
|
||||
|
||||
if(cslIDs.length == 0) {
|
||||
return;
|
||||
} else if(cslIDs.length == 1) {
|
||||
var selectedStyle = Zotero.Styles.get(cslIDs[0])
|
||||
var text = Zotero.getString('styles.deleteStyle', selectedStyle.title);
|
||||
} else {
|
||||
var text = Zotero.getString('styles.deleteStyles');
|
||||
}
|
||||
|
||||
var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
|
||||
.getService(Components.interfaces.nsIPromptService);
|
||||
var text = Zotero.getString('styles.deleteStyle', [title]);
|
||||
if(ps.confirm(null, '', text)) {
|
||||
Zotero.Styles.get(cslID).delete();
|
||||
// delete if requested
|
||||
if(cslIDs.length == 1) {
|
||||
selectedStyle.delete();
|
||||
} else {
|
||||
for(var i=0; i<cslIDs.length; i++) {
|
||||
Zotero.Styles.get(cslIDs[i]).delete();
|
||||
}
|
||||
}
|
||||
|
||||
this.refreshStylesList();
|
||||
document.getElementById('styleManager-delete').disabled = true;
|
||||
}
|
||||
|
||||
document.getElementById('styleManager-delete').disabled = true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1242,6 +1226,8 @@ function styleImportError() {
|
|||
alert(Zotero.getString('styles.installError', "This"));
|
||||
}
|
||||
|
||||
/**PROXIES**/
|
||||
|
||||
/**
|
||||
* Adds a proxy to the proxy pane
|
||||
*/
|
||||
|
|
|
@ -20,342 +20,6 @@
|
|||
***** END LICENSE BLOCK *****
|
||||
*/
|
||||
|
||||
/**
|
||||
* @property {Boolean} cacheTranslatorData Whether translator data should be cached or reloaded
|
||||
* every time a translator is accessed
|
||||
* @property {Zotero.CSL} lastCSL
|
||||
*/
|
||||
Zotero.Styles = new function() {
|
||||
var _initialized = false;
|
||||
var _styles, _visibleStyles;
|
||||
|
||||
this.ios = Components.classes["@mozilla.org/network/io-service;1"].
|
||||
getService(Components.interfaces.nsIIOService);
|
||||
|
||||
/**
|
||||
* Initializes styles cache, loading metadata for styles into memory
|
||||
*/
|
||||
this.init = function() {
|
||||
_initialized = true;
|
||||
|
||||
var start = (new Date()).getTime()
|
||||
|
||||
_styles = {};
|
||||
_visibleStyles = [];
|
||||
this.cacheTranslatorData = Zotero.Prefs.get("cacheTranslatorData");
|
||||
this.lastCSL = null;
|
||||
|
||||
// main dir
|
||||
var dir = Zotero.getStylesDirectory();
|
||||
var i = _readStylesFromDirectory(dir, false);
|
||||
|
||||
// hidden dir
|
||||
dir.append("hidden");
|
||||
if(dir.exists()) i += _readStylesFromDirectory(dir, true);
|
||||
|
||||
Zotero.debug("Cached "+i+" styles in "+((new Date()).getTime() - start)+" ms");
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads all styles from a given directory and caches their metadata
|
||||
*/
|
||||
function _readStylesFromDirectory(dir, hidden) {
|
||||
var i = 0;
|
||||
var contents = dir.directoryEntries;
|
||||
while(contents.hasMoreElements()) {
|
||||
var file = contents.getNext().QueryInterface(Components.interfaces.nsIFile);
|
||||
if(!file.leafName || file.leafName[0] == "." || file.isDirectory()) continue;
|
||||
|
||||
var style = new Zotero.Style(file);
|
||||
if(style.styleID) {
|
||||
if(_styles[style.styleID]) {
|
||||
// same style is already cached
|
||||
Zotero.log('Style with ID '+style.styleID+' already loaded from "'+
|
||||
_styles[style.styleID].file.leafName+'"', "error",
|
||||
Zotero.Styles.ios.newFileURI(style.file).spec);
|
||||
} else {
|
||||
// add to cache
|
||||
_styles[style.styleID] = style;
|
||||
_styles[style.styleID].hidden = hidden;
|
||||
if(!hidden) _visibleStyles.push(style);
|
||||
}
|
||||
}
|
||||
i++;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a style with a given ID
|
||||
* @param {String} id
|
||||
*/
|
||||
this.get = function(id) {
|
||||
if(!_initialized) this.init();
|
||||
return _styles[id] ? _styles[id] : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all visible styles
|
||||
*/
|
||||
this.getVisible = function() {
|
||||
if(!_initialized || !this.cacheTranslatorData) this.init();
|
||||
return _visibleStyles.slice(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all styles
|
||||
*/
|
||||
this.getAll = function() {
|
||||
if(!_initialized || !this.cacheTranslatorData) this.init();
|
||||
return _styles;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @class Represents a style file and its metadata
|
||||
* @property {String} styleID
|
||||
* @property {String} type "csl" for CSL styles, "ens" for legacy styles
|
||||
* @property {String} title
|
||||
* @property {String} updated SQL-style date updated
|
||||
* @property {String} class "in-text" or "note"
|
||||
* @property {String} source The CSL that contains the formatting information for this one, or null
|
||||
* if this CSL contains formatting information
|
||||
* @property {Zotero.CSL} csl The Zotero.CSL object used to format using this style
|
||||
* @property {Boolean} hidden True if this style is hidden in style selection dialogs, false if it
|
||||
* is not
|
||||
*/
|
||||
Zotero.Style = function(file) {
|
||||
this.file = file;
|
||||
|
||||
var extension = file.leafName.substr(-4).toLowerCase();
|
||||
if(extension == ".ens") {
|
||||
this.type = "ens";
|
||||
|
||||
this.styleID = Zotero.Styles.ios.newFileURI(this.file).spec;
|
||||
this.title = file.leafName.substr(0, file.leafName.length-4);
|
||||
this.updated = Zotero.Date.dateToSQL(new Date(file.lastModifiedTime));
|
||||
} else if(extension == ".csl") {
|
||||
// "with ({});" needed to fix default namespace scope issue
|
||||
// See https://bugzilla.mozilla.org/show_bug.cgi?id=330572
|
||||
default xml namespace = "http://purl.org/net/xbiblio/csl"; with ({});
|
||||
|
||||
this.type = "csl";
|
||||
|
||||
var xml = Zotero.CSL.Global.cleanXML(Zotero.File.getContents(file));
|
||||
try {
|
||||
xml = new XML(xml);
|
||||
} catch(e) {
|
||||
Zotero.log(e.toString(), "error",
|
||||
Zotero.Styles.ios.newFileURI(this.file).spec, xml.split(/\r?\n/)[e.lineNumber-1],
|
||||
e.lineNumber);
|
||||
return;
|
||||
}
|
||||
|
||||
this.styleID = xml.info.id.toString();
|
||||
this.title = xml.info.title.toString();
|
||||
this.updated = xml.info.updated.toString().replace(/(.+)T([^\+]+)\+?.*/, "$1 $2");
|
||||
this._class = xml.@class.toString();
|
||||
|
||||
this.source = null;
|
||||
for each(var link in xml.info.link) {
|
||||
if(link.@rel == "source") {
|
||||
this.source = link.@href.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Zotero.Style.prototype.__defineGetter__("csl",
|
||||
/**
|
||||
* Retrieves the Zotero.CSL object for this style
|
||||
* @type Zotero.CSL
|
||||
*/
|
||||
function() {
|
||||
// cache last style
|
||||
if(Zotero.Styles.cacheTranslatorData && Zotero.Styles.lastCSL &&
|
||||
Zotero.Styles.lastCSL.styleID == this.styleID) {
|
||||
return Zotero.Styles.lastCSL;
|
||||
}
|
||||
|
||||
if(this.type == "ens") {
|
||||
// EN style
|
||||
var iStream = Components.classes["@mozilla.org/network/file-input-stream;1"]
|
||||
.createInstance(Components.interfaces.nsIFileInputStream);
|
||||
iStream.init(this.file, 0x01, 0664, 0);
|
||||
var bStream = Components.classes["@mozilla.org/binaryinputstream;1"]
|
||||
.createInstance(Components.interfaces.nsIBinaryInputStream);
|
||||
bStream.setInputStream(iStream);
|
||||
var string = bStream.readBytes(this.file.fileSize);
|
||||
iStream.close();
|
||||
|
||||
var enConverter = new Zotero.ENConverter(string, null, this.title);
|
||||
var xml = enConverter.parse();
|
||||
} else {
|
||||
// "with ({});" needed to fix default namespace scope issue
|
||||
// See https://bugzilla.mozilla.org/show_bug.cgi?id=330572
|
||||
default xml namespace = "http://purl.org/net/xbiblio/csl"; with ({});
|
||||
|
||||
if(this.source) {
|
||||
// parent/child
|
||||
var formatCSL = Zotero.Styles.get(this.source);
|
||||
if(!formatCSL) {
|
||||
throw(new Error('Style references '+this.source+', but this style is not installed',
|
||||
Zotero.Styles.ios.newFileURI(this.file).spec, null));
|
||||
}
|
||||
var file = formatCSL.file;
|
||||
} else {
|
||||
var file = this.file;
|
||||
}
|
||||
|
||||
var cslString = Zotero.File.getContents(file);
|
||||
var xml = new XML(Zotero.CSL.Global.cleanXML(cslString));
|
||||
}
|
||||
|
||||
return (Zotero.Styles.lastCSL = new Zotero.CSL(xml));
|
||||
});
|
||||
|
||||
Zotero.Style.prototype.__defineGetter__("class",
|
||||
/**
|
||||
* Retrieves the style class, either from the metadata that's already loaded or by loading the file
|
||||
* @type String
|
||||
*/
|
||||
function() {
|
||||
if(this._class) return this._class;
|
||||
return (this._class = this.csl.class);
|
||||
});
|
||||
|
||||
/**
|
||||
* Deletes a style
|
||||
*/
|
||||
Zotero.Style.prototype.delete = function() {
|
||||
this.file.remove(false);
|
||||
Zotero.Styles.init();
|
||||
}
|
||||
|
||||
|
||||
Zotero.Styles.MIMEHandler = new function () {
|
||||
this.init = init;
|
||||
|
||||
/*
|
||||
* registers URIContentListener to handle MIME types
|
||||
*/
|
||||
function init() {
|
||||
Zotero.debug("Registering URIContentListener for text/x-csl");
|
||||
var uriLoader = Components.classes["@mozilla.org/uriloader;1"]
|
||||
.getService(Components.interfaces.nsIURILoader);
|
||||
uriLoader.registerContentListener(Zotero.Styles.MIMEHandler.URIContentListener);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Zotero.Styles.MIMEHandler.URIContentListener: implements
|
||||
* nsIURIContentListener interface to grab MIME types
|
||||
*/
|
||||
Zotero.Styles.MIMEHandler.URIContentListener = new function() {
|
||||
// list of content types to capture
|
||||
// NOTE: must be from shortest to longest length
|
||||
this.desiredContentTypes = ["text/x-csl"];
|
||||
|
||||
this.QueryInterface = QueryInterface;
|
||||
this.canHandleContent = canHandleContent;
|
||||
this.doContent = doContent;
|
||||
this.isPreferred = isPreferred;
|
||||
this.onStartURIOpen = onStartURIOpen;
|
||||
|
||||
function QueryInterface(iid) {
|
||||
if (iid.equals(Components.interfaces.nsISupports)
|
||||
|| iid.equals(Components.interfaces.nsISupportsWeakReference)
|
||||
|| iid.equals(Components.interfaces.nsIURIContentListener)) {
|
||||
return this;
|
||||
}
|
||||
throw Components.results.NS_ERROR_NO_INTERFACE;
|
||||
}
|
||||
|
||||
function canHandleContent(contentType, isContentPreferred, desiredContentType) {
|
||||
if (this.desiredContentTypes.indexOf(contentType) != -1) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function doContent(contentType, isContentPreferred, request, contentHandler) {
|
||||
Zotero.debug("Running doContent() for " + request.name);
|
||||
contentHandler.value = new Zotero.Styles.MIMEHandler.StreamListener(request, contentType);
|
||||
return false;
|
||||
}
|
||||
|
||||
function isPreferred(contentType, desiredContentType) {
|
||||
if (this.desiredContentTypes.indexOf(contentType) != -1) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function onStartURIOpen(URI) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Zotero.Styles.MIMEHandler.StreamListener: implements nsIStreamListener and
|
||||
* nsIRequestObserver interfaces to download MIME types we've grabbed
|
||||
*/
|
||||
Zotero.Styles.MIMEHandler.StreamListener = function(request, contentType) {
|
||||
this._request = request;
|
||||
this._contentType = contentType
|
||||
this._readString = "";
|
||||
this._scriptableStream = null;
|
||||
this._scriptableStreamInput = null
|
||||
|
||||
Zotero.debug("Prepared to grab content type " + contentType);
|
||||
}
|
||||
|
||||
Zotero.Styles.MIMEHandler.StreamListener.prototype.QueryInterface = function(iid) {
|
||||
if (iid.equals(Components.interfaces.nsISupports)
|
||||
|| iid.equals(Components.interfaces.nsIRequestObserver)
|
||||
|| iid.equals(Components.interfaces.nsIStreamListener)) {
|
||||
return this;
|
||||
}
|
||||
throw Components.results.NS_ERROR_NO_INTERFACE;
|
||||
}
|
||||
|
||||
Zotero.Styles.MIMEHandler.StreamListener.prototype.onStartRequest = function(channel, context) {}
|
||||
|
||||
/*
|
||||
* Called when there's data available; basically, we just want to collect this data
|
||||
*/
|
||||
Zotero.Styles.MIMEHandler.StreamListener.prototype.onDataAvailable = function(request, context, inputStream, offset, count) {
|
||||
Zotero.debug(count + " bytes available");
|
||||
|
||||
if (inputStream != this._scriptableStreamInput) {
|
||||
this._scriptableStream = Components.classes["@mozilla.org/scriptableinputstream;1"]
|
||||
.createInstance(Components.interfaces.nsIScriptableInputStream);
|
||||
this._scriptableStream.init(inputStream);
|
||||
this._scriptableStreamInput = inputStream;
|
||||
}
|
||||
this._readString += this._scriptableStream.read(count);
|
||||
}
|
||||
|
||||
/*
|
||||
* Called when the request is done
|
||||
*/
|
||||
Zotero.Styles.MIMEHandler.StreamListener.prototype.onStopRequest = function(channel, context, status) {
|
||||
Zotero.debug("Request finished");
|
||||
var externalHelperAppService = Components.classes["@mozilla.org/uriloader/external-helper-app-service;1"]
|
||||
.getService(Components.interfaces.nsIExternalHelperAppService);
|
||||
|
||||
if (this._request.name) {
|
||||
var loadURI = this._request.name;
|
||||
}
|
||||
else {
|
||||
var loadURI = '';
|
||||
}
|
||||
|
||||
Zotero.Styles.install(this._readString, loadURI);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* CSL: a class for creating bibliographies from CSL files
|
||||
* this is abstracted as a separate class for the benefit of anyone who doesn't
|
|
@ -78,6 +78,22 @@ Zotero.File = new function(){
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get contents of a binary file
|
||||
*/
|
||||
this.getBinaryContents = function(file) {
|
||||
var iStream = Components.classes["@mozilla.org/network/file-input-stream;1"]
|
||||
.createInstance(Components.interfaces.nsIFileInputStream);
|
||||
iStream.init(file, 0x01, 0664, 0);
|
||||
var bStream = Components.classes["@mozilla.org/binaryinputstream;1"]
|
||||
.createInstance(Components.interfaces.nsIBinaryInputStream);
|
||||
bStream.setInputStream(iStream);
|
||||
var string = bStream.readBytes(file.fileSize);
|
||||
iStream.close();
|
||||
return string;
|
||||
}
|
||||
|
||||
|
||||
function getContents(file, charset, maxLength){
|
||||
var fis = Components.classes["@mozilla.org/network/file-input-stream;1"].
|
||||
createInstance(Components.interfaces.nsIFileInputStream);
|
||||
|
|
|
@ -17,14 +17,40 @@
|
|||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
|
||||
Utilities based in part on code taken from Piggy Bank 2.1.1 (BSD-licensed)
|
||||
|
||||
|
||||
***** END LICENSE BLOCK *****
|
||||
*/
|
||||
|
||||
Zotero.Ingester = new Object();
|
||||
Zotero.Ingester = new function() {
|
||||
this.importHandler = function(string, uri) {
|
||||
// attempt to import through Zotero.Translate
|
||||
var translation = new Zotero.Translate("import");
|
||||
translation.setLocation(uri);
|
||||
translation.setString(string);
|
||||
|
||||
var frontWindow = Components.classes["@mozilla.org/embedcomp/window-watcher;1"].
|
||||
getService(Components.interfaces.nsIWindowWatcher).activeWindow;
|
||||
|
||||
frontWindow.Zotero_Browser.progress.show();
|
||||
var saveLocation = null;
|
||||
try {
|
||||
saveLocation = frontWindow.ZoteroPane.getSelectedCollection();
|
||||
} catch(e) {}
|
||||
translation.setHandler("itemDone", function(obj, item) { frontWindow.Zotero_Browser.itemDone(obj, item, saveLocation) });
|
||||
translation.setHandler("done", function(obj, item) { frontWindow.Zotero_Browser.finishScraping(obj, item, saveLocation) });
|
||||
|
||||
// attempt to retrieve translators
|
||||
var translators = translation.getTranslators();
|
||||
if(!translators.length) {
|
||||
// we lied. we can't really translate this file.
|
||||
frontWindow.Zotero_Browser.progress.close();
|
||||
throw "No translator found for handled RIS or Refer file"
|
||||
}
|
||||
|
||||
// translate using first available
|
||||
translation.setTranslator(translators[0]);
|
||||
translation.translate();
|
||||
}
|
||||
}
|
||||
|
||||
Zotero.OpenURL = new function() {
|
||||
this.resolve = resolve;
|
||||
|
@ -457,176 +483,4 @@ Zotero.OpenURL = new function() {
|
|||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Zotero.Ingester.MIMEHandler = new function() {
|
||||
var on = false;
|
||||
|
||||
this.init = init;
|
||||
|
||||
/*
|
||||
* registers URIContentListener to handle MIME types
|
||||
*/
|
||||
function init() {
|
||||
var prefStatus = Zotero.Prefs.get("parseEndNoteMIMETypes");
|
||||
if(!on && prefStatus) {
|
||||
Zotero.debug("Registering URIContentListener for RIS/Refer");
|
||||
var uriLoader = Components.classes["@mozilla.org/uriloader;1"].
|
||||
getService(Components.interfaces.nsIURILoader);
|
||||
uriLoader.registerContentListener(Zotero.Ingester.MIMEHandler.URIContentListener);
|
||||
on = true;
|
||||
} else if(on && !prefStatus) {
|
||||
Zotero.debug("Unregistering URIContentListener for RIS/Refer");
|
||||
var uriLoader = Components.classes["@mozilla.org/uriloader;1"].
|
||||
getService(Components.interfaces.nsIURILoader);
|
||||
uriLoader.unRegisterContentListener(Zotero.Ingester.MIMEHandler.URIContentListener);
|
||||
on = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Zotero.Ingester.MIMEHandler.URIContentListener: implements
|
||||
* nsIURIContentListener interface to grab MIME types
|
||||
*/
|
||||
Zotero.Ingester.MIMEHandler.URIContentListener = new function() {
|
||||
// list of content types to capture
|
||||
// NOTE: must be from shortest to longest length
|
||||
this.desiredContentTypes = ["application/x-endnote-refer",
|
||||
"application/x-research-info-systems"];
|
||||
|
||||
this.QueryInterface = QueryInterface;
|
||||
this.canHandleContent = canHandleContent;
|
||||
this.doContent = doContent;
|
||||
this.isPreferred = isPreferred;
|
||||
this.onStartURIOpen = onStartURIOpen;
|
||||
|
||||
function QueryInterface(iid) {
|
||||
if(iid.equals(Components.interfaces.nsISupports)
|
||||
|| iid.equals(Components.interfaces.nsISupportsWeakReference)
|
||||
|| iid.equals(Components.interfaces.nsIURIContentListener)) {
|
||||
return this;
|
||||
}
|
||||
throw Components.results.NS_ERROR_NO_INTERFACE;
|
||||
}
|
||||
|
||||
function canHandleContent(contentType, isContentPreferred, desiredContentType) {
|
||||
if(Zotero.inArray(contentType, this.desiredContentTypes)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function doContent(contentType, isContentPreferred, request, contentHandler) {
|
||||
Zotero.debug("doing content for "+request.name);
|
||||
contentHandler.value = new Zotero.Ingester.MIMEHandler.StreamListener(request, contentType);
|
||||
return false;
|
||||
}
|
||||
|
||||
function isPreferred(contentType, desiredContentType) {
|
||||
if(Zotero.inArray(contentType, this.desiredContentTypes)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function onStartURIOpen(URI) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Zotero.Ingester.MIMEHandler.StreamListener: implements nsIStreamListener and
|
||||
* nsIRequestObserver interfaces to download MIME types we've grabbed
|
||||
*/
|
||||
Zotero.Ingester.MIMEHandler.StreamListener = function(request, contentType) {
|
||||
this._request = request;
|
||||
this._contentType = contentType
|
||||
this._readString = "";
|
||||
this._scriptableStream = null;
|
||||
this._scriptableStreamInput = null
|
||||
|
||||
// get front window
|
||||
var windowWatcher = Components.classes["@mozilla.org/embedcomp/window-watcher;1"].
|
||||
getService(Components.interfaces.nsIWindowWatcher);
|
||||
this._frontWindow = windowWatcher.activeWindow;
|
||||
this._frontWindow.Zotero_Browser.progress.show();
|
||||
|
||||
Zotero.debug("EndNote prepared to grab content type "+contentType);
|
||||
}
|
||||
|
||||
Zotero.Ingester.MIMEHandler.StreamListener.prototype.QueryInterface = function(iid) {
|
||||
if(iid.equals(Components.interfaces.nsISupports)
|
||||
|| iid.equals(Components.interfaces.nsIRequestObserver)
|
||||
|| iid.equals(Components.interfaces.nsIStreamListener)) {
|
||||
return this;
|
||||
}
|
||||
throw Components.results.NS_ERROR_NO_INTERFACE;
|
||||
}
|
||||
|
||||
Zotero.Ingester.MIMEHandler.StreamListener.prototype.onStartRequest = function(channel, context) {}
|
||||
|
||||
/*
|
||||
* called when there's data available; basicallly, we just want to collect this data
|
||||
*/
|
||||
Zotero.Ingester.MIMEHandler.StreamListener.prototype.onDataAvailable = function(request, context, inputStream, offset, count) {
|
||||
Zotero.debug(count+" bytes available");
|
||||
|
||||
if(inputStream != this._scriptableStreamInput) { // get storage stream
|
||||
// if there's not one
|
||||
this._scriptableStream = Components.classes["@mozilla.org/scriptableinputstream;1"].
|
||||
createInstance(Components.interfaces.nsIScriptableInputStream);
|
||||
this._scriptableStream.init(inputStream);
|
||||
this._scriptableStreamInput = inputStream;
|
||||
}
|
||||
this._readString += this._scriptableStream.read(count);
|
||||
}
|
||||
|
||||
/*
|
||||
* called when the request is done
|
||||
*/
|
||||
Zotero.Ingester.MIMEHandler.StreamListener.prototype.onStopRequest = function(channel, context, status) {
|
||||
Zotero.debug("request finished");
|
||||
var externalHelperAppService = Components.classes["@mozilla.org/uriloader/external-helper-app-service;1"].
|
||||
getService(Components.interfaces.nsIExternalHelperAppService);
|
||||
|
||||
// attempt to import through Zotero.Translate
|
||||
var translation = new Zotero.Translate("import");
|
||||
translation.setLocation(this._request.name);
|
||||
translation.setString(this._readString);
|
||||
|
||||
// use front window's save functions and folder
|
||||
var frontWindow = this._frontWindow;
|
||||
|
||||
var saveLocation = null;
|
||||
try {
|
||||
saveLocation = frontWindow.ZoteroPane.getSelectedCollection();
|
||||
} catch(e) {}
|
||||
translation.setHandler("itemDone", function(obj, item) { frontWindow.Zotero_Browser.itemDone(obj, item, saveLocation) });
|
||||
translation.setHandler("done", function(obj, item) { frontWindow.Zotero_Browser.finishScraping(obj, item, saveLocation) });
|
||||
|
||||
// attempt to retrieve translators
|
||||
var translators = translation.getTranslators();
|
||||
if(!translators.length) {
|
||||
// we lied. we can't really translate this file. call
|
||||
// nsIExternalHelperAppService with the data
|
||||
frontWindow.Zotero_Browser.progress.close();
|
||||
|
||||
var streamListener;
|
||||
if(streamListener = externalHelperAppService.doContent(this._contentType, this._request, frontWindow)) {
|
||||
// create a string input stream
|
||||
var inputStream = Components.classes["@mozilla.org/io/string-input-stream;1"].
|
||||
createInstance(Components.interfaces.nsIStringInputStream);
|
||||
inputStream.setData(this._readString, this._readString.length);
|
||||
|
||||
streamListener.onStartRequest(channel, context);
|
||||
streamListener.onDataAvailable(this._request, context, inputStream, 0, this._readString.length);
|
||||
streamListener.onStopRequest(channel, context, status);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// translate using first available
|
||||
translation.setTranslator(translators[0]);
|
||||
translation.translate();
|
||||
}
|
220
chrome/content/zotero/xpcom/mimeTypeHandler.js
Normal file
220
chrome/content/zotero/xpcom/mimeTypeHandler.js
Normal file
|
@ -0,0 +1,220 @@
|
|||
/*
|
||||
***** 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.
|
||||
|
||||
***** END LICENSE BLOCK *****
|
||||
*/
|
||||
|
||||
Zotero.MIMETypeHandler = new function () {
|
||||
var _typeHandlers, _ignoreContentDispositionTypes, _observers;
|
||||
|
||||
/**
|
||||
* Registers URIContentListener to handle MIME types
|
||||
*/
|
||||
this.init = function() {
|
||||
Zotero.debug("Registering URIContentListener");
|
||||
// register our nsIURIContentListener and nsIObserver
|
||||
Components.classes["@mozilla.org/uriloader;1"].
|
||||
getService(Components.interfaces.nsIURILoader).
|
||||
registerContentListener(_URIContentListener);
|
||||
Components.classes["@mozilla.org/observer-service;1"].
|
||||
getService(Components.interfaces.nsIObserverService).
|
||||
addObserver(_Observer, "http-on-examine-response", false);
|
||||
this.initializeHandlers();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes handlers for MIME types
|
||||
*/
|
||||
this.initializeHandlers = function() {
|
||||
_typeHandlers = {};
|
||||
_ignoreContentDispositionTypes = [];
|
||||
_observers = [];
|
||||
|
||||
if(Zotero.Prefs.get("parseEndNoteMIMETypes")) {
|
||||
this.addHandler("application/x-endnote-refer", Zotero.Ingester.importHandler, true);
|
||||
this.addHandler("application/x-research-info-systems", Zotero.Ingester.importHandler, true);
|
||||
}
|
||||
this.addHandler("text/x-csl", function(a1, a2, a3) { Zotero.Styles.install(a1, a2, a3) });
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a handler to handle a specific MIME type
|
||||
* @param {String} type MIME type to handle
|
||||
* @param {Function} fn Function to call to handle type
|
||||
* @param {Boolean} ignoreContentDisposition If true, ignores the Content-Disposition header,
|
||||
* which is often used to force a file to download rather than let it be handled by the web
|
||||
* browser
|
||||
*/
|
||||
this.addHandler = function(type, fn, ignoreContentDisposition) {
|
||||
_typeHandlers[type] = fn;
|
||||
_ignoreContentDispositionTypes.push(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an observer to inspect and possibly modify page headers
|
||||
*/
|
||||
this.addObserver = function(fn) {
|
||||
_observers.push(fn);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to observe a page load
|
||||
*/
|
||||
var _Observer = new function() {
|
||||
this.observe = function(channel) {
|
||||
channel.QueryInterface(Components.interfaces.nsIRequest);
|
||||
if(channel.loadFlags & Components.interfaces.nsIHttpChannel.LOAD_DOCUMENT_URI) {
|
||||
channel.QueryInterface(Components.interfaces.nsIHttpChannel);
|
||||
try {
|
||||
// remove content-disposition headers for EndNote, etc.
|
||||
var contentType = channel.getResponseHeader("Content-Type").toLowerCase();
|
||||
for each(var handledType in _ignoreContentDispositionTypes) {
|
||||
if(contentType.length < handledType.length) {
|
||||
break;
|
||||
} else {
|
||||
if(contentType.substr(0, handledType.length) == handledType) {
|
||||
channel.setResponseHeader("Content-Disposition", "", false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch(e) {}
|
||||
|
||||
for each(var observer in _observers) {
|
||||
observer(channel);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var _URIContentListener = new function() {
|
||||
/**
|
||||
* Standard QI definiton
|
||||
*/
|
||||
this.QueryInterface = function(iid) {
|
||||
if (iid.equals(Components.interfaces.nsISupports)
|
||||
|| iid.equals(Components.interfaces.nsISupportsWeakReference)
|
||||
|| iid.equals(Components.interfaces.nsIURIContentListener)) {
|
||||
return this;
|
||||
}
|
||||
throw Components.results.NS_ERROR_NO_INTERFACE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to see if we can handle a content type
|
||||
*/
|
||||
this.canHandleContent = this.isPreferred = function(contentType, isContentPreferred, desiredContentType) {
|
||||
return !!_typeHandlers[contentType.toLowerCase()];
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to begin handling a content type
|
||||
*/
|
||||
this.doContent = function(contentType, isContentPreferred, request, contentHandler) {
|
||||
Zotero.debug("MIMETypeHandler: handling "+contentType+" from " + request.name);
|
||||
contentHandler.value = new _StreamListener(request, contentType.toLowerCase());
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called so that we could stop a load before it happened if we wanted to
|
||||
*/
|
||||
this.onStartURIOpen = function(URI) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @class Implements nsIStreamListener and nsIRequestObserver interfaces to download MIME types
|
||||
* we've registered ourself as the handler for
|
||||
* @param {nsIRequest} request The request to handle
|
||||
* @param {String} contenType The content type being handled
|
||||
*/
|
||||
var _StreamListener = function(request, contentType) {
|
||||
this._request = request;
|
||||
this._contentType = contentType
|
||||
this._readString = "";
|
||||
this._scriptableStream = null;
|
||||
this._scriptableStreamInput = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Standard QI definiton
|
||||
*/
|
||||
_StreamListener.prototype.QueryInterface = function(iid) {
|
||||
if (iid.equals(Components.interfaces.nsISupports)
|
||||
|| iid.equals(Components.interfaces.nsIRequestObserver)
|
||||
|| iid.equals(Components.interfaces.nsIStreamListener)) {
|
||||
return this;
|
||||
}
|
||||
throw Components.results.NS_ERROR_NO_INTERFACE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the request is started; we ignore this
|
||||
*/
|
||||
_StreamListener.prototype.onStartRequest = function(channel, context) {}
|
||||
|
||||
|
||||
/**
|
||||
* Called when there's data available; we collect this data and keep it until the request is
|
||||
* done
|
||||
*/
|
||||
_StreamListener.prototype.onDataAvailable = function(request, context, inputStream, offset, count) {
|
||||
if (inputStream != this._scriptableStreamInput) {
|
||||
this._scriptableStream = Components.classes["@mozilla.org/scriptableinputstream;1"]
|
||||
.createInstance(Components.interfaces.nsIScriptableInputStream);
|
||||
this._scriptableStream.init(inputStream);
|
||||
this._scriptableStreamInput = inputStream;
|
||||
}
|
||||
this._readString += this._scriptableStream.read(count);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the request is done
|
||||
*/
|
||||
_StreamListener.prototype.onStopRequest = function(channel, context, status) {
|
||||
try {
|
||||
_typeHandlers[this._contentType](this._readString, (this._request.name ? this._request.name : null),
|
||||
this._contentType);
|
||||
} catch(e) {
|
||||
// if there was an error, handle using nsIExternalHelperAppService
|
||||
var externalHelperAppService = Components.classes["@mozilla.org/uriloader/external-helper-app-service;1"].
|
||||
getService(Components.interfaces.nsIExternalHelperAppService);
|
||||
var frontWindow = Components.classes["@mozilla.org/embedcomp/window-watcher;1"].
|
||||
getService(Components.interfaces.nsIWindowWatcher).activeWindow;
|
||||
|
||||
var newStreamListener = externalHelperAppService.doContent(this._contentType,
|
||||
this._request, frontWindow, false);
|
||||
if(newStreamListener) {
|
||||
// create a string input stream
|
||||
var inputStream = Components.classes["@mozilla.org/io/string-input-stream;1"].
|
||||
createInstance(Components.interfaces.nsIStringInputStream);
|
||||
inputStream.setData(this._readString, this._readString.length);
|
||||
|
||||
newStreamListener.onStartRequest(channel, context);
|
||||
newStreamListener.onDataAvailable(this._request, context, inputStream, 0, this._readString.length);
|
||||
newStreamListener.onStopRequest(channel, context, status);
|
||||
}
|
||||
|
||||
// then throw our error
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -44,12 +44,11 @@ Zotero.Proxies = new function() {
|
|||
*/
|
||||
this.init = function() {
|
||||
if(!on) {
|
||||
var observerService = Components.classes["@mozilla.org/observer-service;1"]
|
||||
.getService(Components.interfaces.nsIObserverService);
|
||||
observerService.addObserver(this, "http-on-examine-response", false);
|
||||
var me = this;
|
||||
Zotero.MIMETypeHandler.addObserver(function(ch) { me.observe(ch) });
|
||||
this.get();
|
||||
on = true;
|
||||
}
|
||||
on = true;
|
||||
|
||||
autoRecognize = Zotero.Prefs.get("proxies.autoRecognize");
|
||||
transparent = Zotero.Prefs.get("proxies.transparent");
|
||||
|
@ -63,94 +62,73 @@ Zotero.Proxies = new function() {
|
|||
* @param {nsIChannel} channel
|
||||
*/
|
||||
this.observe = function(channel) {
|
||||
channel.QueryInterface(Components.interfaces.nsIHttpChannel);
|
||||
try {
|
||||
// remove content-disposition headers for endnote, etc.
|
||||
var contentType = channel.getResponseHeader("Content-Type").toLowerCase();
|
||||
for each(var desiredContentType in Zotero.Ingester.MIMEHandler.URIContentListener.desiredContentTypes) {
|
||||
if(contentType.length < desiredContentType.length) {
|
||||
break;
|
||||
} else {
|
||||
if(contentType.substr(0, desiredContentType.length) == desiredContentType) {
|
||||
channel.setResponseHeader("Content-Disposition", "", false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch(e) {}
|
||||
|
||||
// try to detect a proxy
|
||||
channel.QueryInterface(Components.interfaces.nsIRequest);
|
||||
if(channel.loadFlags & Components.interfaces.nsIHttpChannel.LOAD_DOCUMENT_URI) {
|
||||
channel.QueryInterface(Components.interfaces.nsIHttpChannel);
|
||||
var url = channel.URI.spec;
|
||||
channel.QueryInterface(Components.interfaces.nsIHttpChannel);
|
||||
var url = channel.URI.spec;
|
||||
|
||||
// see if there is a proxy we already know
|
||||
var m = false;
|
||||
var proxy;
|
||||
for each(proxy in proxies) {
|
||||
if(proxy.regexp && proxy.multiHost) {
|
||||
m = proxy.regexp.exec(url);
|
||||
if(m) break;
|
||||
// see if there is a proxy we already know
|
||||
var m = false;
|
||||
var proxy;
|
||||
for each(proxy in proxies) {
|
||||
if(proxy.regexp && proxy.multiHost) {
|
||||
m = proxy.regexp.exec(url);
|
||||
if(m) break;
|
||||
}
|
||||
}
|
||||
|
||||
if(m) {
|
||||
// add this host if we know a proxy
|
||||
if(proxy.autoAssociate) {
|
||||
var host = m[proxy.parameters.indexOf("%h")+1];
|
||||
if(proxy.hosts.indexOf(host) == -1) {
|
||||
proxy.hosts.push(host);
|
||||
proxy.save();
|
||||
}
|
||||
}
|
||||
|
||||
if(m) {
|
||||
// add this host if we know a proxy
|
||||
if(proxy.autoAssociate) {
|
||||
var host = m[proxy.parameters.indexOf("%h")+1];
|
||||
if(proxy.hosts.indexOf(host) == -1) {
|
||||
proxy.hosts.push(host);
|
||||
proxy.save();
|
||||
}
|
||||
} else if(autoRecognize) {
|
||||
// otherwise, try to detect a proxy
|
||||
var proxy = false;
|
||||
for each(var detector in Zotero.Proxies.Detectors) {
|
||||
try {
|
||||
proxy = detector(channel);
|
||||
} catch(e) {
|
||||
Components.utils.reportError(e);
|
||||
}
|
||||
} else if(autoRecognize) {
|
||||
// otherwise, try to detect a proxy
|
||||
var proxy = false;
|
||||
for each(var detector in Zotero.Proxies.Detectors) {
|
||||
try {
|
||||
proxy = detector(channel);
|
||||
} catch(e) {
|
||||
Components.utils.reportError(e);
|
||||
|
||||
if(!transparent) {
|
||||
// if transparent is turned off, just save the proxy
|
||||
proxy.save();
|
||||
} else if(proxy) {
|
||||
// otherwise, make sure we want it
|
||||
var io = {site:proxy.hosts[0], proxy:channel.URI.hostPort};
|
||||
var window = Components.classes["@mozilla.org/appshell/window-mediator;1"]
|
||||
.getService(Components.interfaces.nsIWindowMediator)
|
||||
.getMostRecentWindow("navigator:browser");
|
||||
window.openDialog('chrome://zotero/content/proxy.xul', '', 'chrome,modal', io);
|
||||
|
||||
if(io.add) proxy.save();
|
||||
if(io.disable) {
|
||||
transparent = false;
|
||||
Zotero.Prefs.set("proxies.transparent", false);
|
||||
}
|
||||
|
||||
if(!transparent) {
|
||||
// if transparent is turned off, just save the proxy
|
||||
proxy.save();
|
||||
} else if(proxy) {
|
||||
// otherwise, make sure we want it
|
||||
var io = {site:proxy.hosts[0], proxy:channel.URI.hostPort};
|
||||
var window = Components.classes["@mozilla.org/appshell/window-mediator;1"]
|
||||
.getService(Components.interfaces.nsIWindowMediator)
|
||||
.getMostRecentWindow("navigator:browser");
|
||||
window.openDialog('chrome://zotero/content/proxy.xul', '', 'chrome,modal', io);
|
||||
|
||||
if(io.add) proxy.save();
|
||||
if(io.disable) {
|
||||
transparent = false;
|
||||
Zotero.Prefs.set("proxies.transparent", false);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// try to get an applicable proxy
|
||||
if(transparent) {
|
||||
var webNav = null;
|
||||
try {
|
||||
webNav = channel.notificationCallbacks.QueryInterface(Components.interfaces.nsIWebNavigation);
|
||||
} catch(e) {}
|
||||
|
||||
if(webNav) {
|
||||
var proxied = this.properToProxy(url, true);
|
||||
if(proxied) webNav.loadURI(proxied, 0, channel.URI, null, null);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
delete channel;
|
||||
// try to get an applicable proxy
|
||||
if(transparent) {
|
||||
var webNav = null;
|
||||
try {
|
||||
webNav = channel.notificationCallbacks.QueryInterface(Components.interfaces.nsIWebNavigation);
|
||||
} catch(e) {}
|
||||
|
||||
if(webNav) {
|
||||
var proxied = this.properToProxy(url, true);
|
||||
if(proxied) webNav.loadURI(proxied, 0, channel.URI, null, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
473
chrome/content/zotero/xpcom/style.js
Normal file
473
chrome/content/zotero/xpcom/style.js
Normal file
|
@ -0,0 +1,473 @@
|
|||
/*
|
||||
***** 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.
|
||||
|
||||
***** END LICENSE BLOCK *****
|
||||
*/
|
||||
|
||||
/**
|
||||
* @property {Boolean} cacheTranslatorData Whether translator data should be cached or reloaded
|
||||
* every time a translator is accessed
|
||||
* @property {Zotero.CSL} lastCSL
|
||||
*/
|
||||
Zotero.Styles = new function() {
|
||||
var _initialized = false;
|
||||
var _styles, _visibleStyles;
|
||||
|
||||
this.ios = Components.classes["@mozilla.org/network/io-service;1"].
|
||||
getService(Components.interfaces.nsIIOService);
|
||||
|
||||
/**
|
||||
* Initializes styles cache, loading metadata for styles into memory
|
||||
*/
|
||||
this.init = function() {
|
||||
_initialized = true;
|
||||
|
||||
var start = (new Date()).getTime()
|
||||
|
||||
_styles = {};
|
||||
_visibleStyles = [];
|
||||
this.cacheTranslatorData = Zotero.Prefs.get("cacheTranslatorData");
|
||||
this.lastCSL = null;
|
||||
|
||||
// main dir
|
||||
var dir = Zotero.getStylesDirectory();
|
||||
var i = _readStylesFromDirectory(dir, false);
|
||||
|
||||
// hidden dir
|
||||
dir.append("hidden");
|
||||
if(dir.exists()) i += _readStylesFromDirectory(dir, true);
|
||||
|
||||
Zotero.debug("Cached "+i+" styles in "+((new Date()).getTime() - start)+" ms");
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads all styles from a given directory and caches their metadata
|
||||
* @privates
|
||||
*/
|
||||
function _readStylesFromDirectory(dir, hidden) {
|
||||
var i = 0;
|
||||
var contents = dir.directoryEntries;
|
||||
while(contents.hasMoreElements()) {
|
||||
var file = contents.getNext().QueryInterface(Components.interfaces.nsIFile);
|
||||
if(!file.leafName || file.leafName[0] == "." || file.isDirectory()) continue;
|
||||
|
||||
var style = new Zotero.Style(file);
|
||||
if(style.styleID) {
|
||||
if(_styles[style.styleID]) {
|
||||
// same style is already cached
|
||||
Zotero.log('Style with ID '+style.styleID+' already loaded from "'+
|
||||
_styles[style.styleID].file.leafName+'"', "error",
|
||||
Zotero.Styles.ios.newFileURI(style.file).spec);
|
||||
} else {
|
||||
// add to cache
|
||||
_styles[style.styleID] = style;
|
||||
_styles[style.styleID].hidden = hidden;
|
||||
if(!hidden) _visibleStyles.push(style);
|
||||
}
|
||||
}
|
||||
i++;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a style with a given ID
|
||||
* @param {String} id
|
||||
*/
|
||||
this.get = function(id) {
|
||||
if(!_initialized) this.init();
|
||||
return _styles[id] ? _styles[id] : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all visible styles
|
||||
* @return {Zotero.Style[]} An array of Zotero.Style objects
|
||||
*/
|
||||
this.getVisible = function() {
|
||||
if(!_initialized || !this.cacheTranslatorData) this.init();
|
||||
return _visibleStyles.slice(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all styles
|
||||
* @return {Object} An object whose keys are style IDs, and whose values are Zotero.Style objects
|
||||
*/
|
||||
this.getAll = function() {
|
||||
if(!_initialized || !this.cacheTranslatorData) this.init();
|
||||
return _styles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Installs a style file
|
||||
* @param {String|nsIFile} style An nsIFile representing a style on disk, or a string containing
|
||||
* the style data
|
||||
* @param {String} loadURI The URI this style file was loaded from
|
||||
* @param {Boolean} hidden Whether style is to be hidden. If this parameter is true, UI alerts
|
||||
* are silenced as well
|
||||
*/
|
||||
this.install = function(style, loadURI, hidden) {
|
||||
// "with ({});" needed to fix default namespace scope issue
|
||||
// See https://bugzilla.mozilla.org/show_bug.cgi?id=330572
|
||||
default xml namespace = "http://purl.org/net/xbiblio/csl"; with ({});
|
||||
const pathRe = /[^\/]+$/;
|
||||
|
||||
if(!_initialized || !this.cacheTranslatorData) this.init();
|
||||
|
||||
var type = "csl";
|
||||
|
||||
// handle nsIFiles
|
||||
var styleFile = null;
|
||||
if(style instanceof Components.interfaces.nsIFile) {
|
||||
styleFile = style;
|
||||
loadURI = style.leafName;
|
||||
if(loadURI.substr(-4) == ".ens") {
|
||||
type = "ens";
|
||||
style = Zotero.File.getBinaryContents(styleFile);
|
||||
} else {
|
||||
style = Zotero.File.getContents(styleFile);
|
||||
}
|
||||
}
|
||||
|
||||
var error = false;
|
||||
try {
|
||||
if(type == "ens") {
|
||||
// EN style
|
||||
var type = "ens";
|
||||
var enConverter = new Zotero.ENConverter(style);
|
||||
var xml = enConverter.parse();
|
||||
} else {
|
||||
// CSL
|
||||
var xml = new XML(Zotero.CSL.Global.cleanXML(style));
|
||||
}
|
||||
} catch(e) {
|
||||
error = e;
|
||||
}
|
||||
|
||||
if(!xml || error) {
|
||||
if(!hidden) alert(Zotero.getString('styles.installErrorURI', loadURI));
|
||||
if(error) throw error;
|
||||
return false;
|
||||
}
|
||||
|
||||
var source = null;
|
||||
var styleID = xml.info.id.toString();
|
||||
if(type == "ens") {
|
||||
var title = styleFile.leafName.substr(0, styleFile.leafName.length-4);
|
||||
var fileName = styleFile.leafName;
|
||||
} else {
|
||||
// get file name from URL
|
||||
var m = pathRe.exec(styleID);
|
||||
var fileName = Zotero.File.getValidFileName(m ? m[0] : styleID);
|
||||
var title = xml.info.title.toString();
|
||||
|
||||
// look for a parent
|
||||
for each(var link in xml.info.link) {
|
||||
if(link.@rel == "source") {
|
||||
source = link.@href.toString();
|
||||
if(source == styleID) {
|
||||
if(!hidden) alert(Zotero.getString('styles.installErrorURI', loadURI));
|
||||
throw "Style with ID "+this.styleID+" references itself as source";
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ensure csl or ens extension
|
||||
if(fileName.substr(-4).toLowerCase() != "."+type) fileName += "."+type;
|
||||
|
||||
var destFile = Zotero.getStylesDirectory();
|
||||
var destFileHidden = destFile.clone();
|
||||
destFile.append(fileName);
|
||||
destFileHidden.append("hidden");
|
||||
if(hidden) Zotero.File.createDirectoryIfMissing(destFileHidden);
|
||||
destFileHidden.append(fileName);
|
||||
|
||||
// look for an existing style with the same styleID or filename
|
||||
var existingFile = null;
|
||||
var existingTitle = null;
|
||||
if(_styles[styleID]) {
|
||||
existingFile = _styles[styleID].file;
|
||||
existingTitle = _styles[styleID].title;
|
||||
} else {
|
||||
if(destFile.exists()) {
|
||||
existingFile = destFile;
|
||||
} else if(destFileHidden.exists()) {
|
||||
existingFile = destFileHidden;
|
||||
}
|
||||
|
||||
if(existingFile) {
|
||||
// find associated style
|
||||
for each(var existingStyle in this._styles) {
|
||||
if(destFile.equals(existingStyle.file)) {
|
||||
existingTitle = existingStyle.title;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// display a dialog to tell the user we're about to install the style
|
||||
if(hidden) {
|
||||
destFile = destFileHidden;
|
||||
} else {
|
||||
var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
|
||||
.getService(Components.interfaces.nsIPromptService);
|
||||
|
||||
if(existingTitle) {
|
||||
var text = Zotero.getString('styles.updateStyleURI', [existingTitle, title, loadURI]);
|
||||
} else {
|
||||
var text = Zotero.getString('styles.installStyleURI', [title, loadURI]);
|
||||
}
|
||||
|
||||
var index = ps.confirmEx(null, '',
|
||||
text,
|
||||
((ps.BUTTON_POS_0) * (ps.BUTTON_TITLE_IS_STRING)
|
||||
+ (ps.BUTTON_POS_1) * (ps.BUTTON_TITLE_CANCEL)),
|
||||
Zotero.getString('general.install'), null, null, null, {}
|
||||
);
|
||||
}
|
||||
|
||||
if(hidden || index == 0) {
|
||||
// user wants to install/update
|
||||
if(source && !_styles[source]) {
|
||||
// need to fetch source
|
||||
if(source.substr(0, 7) == "http://" || source.substr(0, 8) == "https://") {
|
||||
Zotero.Utilities.HTTP.doGet(source, function(xmlhttp) {
|
||||
var success = false;
|
||||
var error = null;
|
||||
try {
|
||||
var success = Zotero.Styles.install(xmlhttp.responseText, loadURI, true);
|
||||
} catch(e) {
|
||||
error = e;
|
||||
}
|
||||
|
||||
if(success) {
|
||||
_completeInstall(style, styleID, destFile, existingFile, styleFile);
|
||||
} else {
|
||||
if(!hidden) alert(Zotero.getString('styles.installSourceErrorURI', [loadURI, source]));
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
if(!hidden) alert(Zotero.getString('styles.installSourceErrorURI', [loadURI, source]));
|
||||
throw "Source CSL URI is invalid";
|
||||
}
|
||||
} else {
|
||||
_completeInstall(style, styleID, destFile, existingFile, styleFile);
|
||||
}
|
||||
return styleID;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finishes installing a style, copying the file, reloading the style cache, and refreshing the
|
||||
* styles list in any open windows
|
||||
* @param {String} style The style string
|
||||
* @param {String} styleID The style ID
|
||||
* @param {nsIFile} destFile The destination for the style
|
||||
* @param {nsIFile} [existingFile] The existing file to delete before copying this one
|
||||
* @param {nsIFile} [styleFile] The file that contains the style to be installed
|
||||
* @private
|
||||
*/
|
||||
function _completeInstall(style, styleID, destFile, existingFile, styleFile) {
|
||||
// remove any existing file with a different name
|
||||
if(existingFile) existingFile.remove(false);
|
||||
|
||||
if(styleFile) {
|
||||
styleFile.copyToFollowingLinks(destFile.parent, destFile.leafName);
|
||||
} else {
|
||||
Zotero.File.putContents(destFile, style);
|
||||
}
|
||||
|
||||
// recache
|
||||
Zotero.Styles.init();
|
||||
|
||||
// refresh preferences windows
|
||||
var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"].
|
||||
getService(Components.interfaces.nsIWindowMediator);
|
||||
var enumerator = wm.getEnumerator("zotero:pref");
|
||||
while(enumerator.hasMoreElements()) {
|
||||
var win = enumerator.getNext();
|
||||
win.refreshStylesList(styleID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @class Represents a style file and its metadata
|
||||
* @property {nsIFile} file The path to the style file
|
||||
* @property {String} styleID
|
||||
* @property {String} type "csl" for CSL styles, "ens" for legacy styles
|
||||
* @property {String} title
|
||||
* @property {String} updated SQL-style date updated
|
||||
* @property {String} class "in-text" or "note"
|
||||
* @property {String} source The CSL that contains the formatting information for this one, or null
|
||||
* if this CSL contains formatting information
|
||||
* @property {Zotero.CSL} csl The Zotero.CSL object used to format using this style
|
||||
* @property {Boolean} hidden True if this style is hidden in style selection dialogs, false if it
|
||||
* is not
|
||||
*/
|
||||
Zotero.Style = function(file) {
|
||||
this.file = file;
|
||||
|
||||
var extension = file.leafName.substr(-4).toLowerCase();
|
||||
if(extension == ".ens") {
|
||||
this.type = "ens";
|
||||
|
||||
this.styleID = Zotero.Styles.ios.newFileURI(this.file).spec;
|
||||
this.title = file.leafName.substr(0, file.leafName.length-4);
|
||||
this.updated = Zotero.Date.dateToSQL(new Date(file.lastModifiedTime));
|
||||
} else if(extension == ".csl") {
|
||||
// "with ({});" needed to fix default namespace scope issue
|
||||
// See https://bugzilla.mozilla.org/show_bug.cgi?id=330572
|
||||
default xml namespace = "http://purl.org/net/xbiblio/csl"; with ({});
|
||||
|
||||
this.type = "csl";
|
||||
|
||||
var xml = Zotero.CSL.Global.cleanXML(Zotero.File.getContents(file));
|
||||
try {
|
||||
xml = new XML(xml);
|
||||
} catch(e) {
|
||||
Zotero.log(e.toString(), "error",
|
||||
Zotero.Styles.ios.newFileURI(this.file).spec, xml.split(/\r?\n/)[e.lineNumber-1],
|
||||
e.lineNumber);
|
||||
return;
|
||||
}
|
||||
|
||||
this.styleID = xml.info.id.toString();
|
||||
this.title = xml.info.title.toString();
|
||||
this.updated = xml.info.updated.toString().replace(/(.+)T([^\+]+)\+?.*/, "$1 $2");
|
||||
this._class = xml.@class.toString();
|
||||
|
||||
this.source = null;
|
||||
for each(var link in xml.info.link) {
|
||||
if(link.@rel == "source") {
|
||||
this.source = link.@href.toString();
|
||||
if(this.source == this.styleID) {
|
||||
throw "Style with ID "+this.styleID+" references itself as source";
|
||||
this.source = null;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Zotero.Style.prototype.__defineGetter__("csl",
|
||||
/**
|
||||
* Retrieves the Zotero.CSL object for this style
|
||||
* @type Zotero.CSL
|
||||
*/
|
||||
function() {
|
||||
// cache last style
|
||||
if(Zotero.Styles.cacheTranslatorData && Zotero.Styles.lastCSL &&
|
||||
Zotero.Styles.lastCSL.styleID == this.styleID) {
|
||||
return Zotero.Styles.lastCSL;
|
||||
}
|
||||
|
||||
if(this.type == "ens") {
|
||||
// EN style
|
||||
var string = Zotero.File.getBinaryContents(this.file);
|
||||
var enConverter = new Zotero.ENConverter(string, null, this.title);
|
||||
var xml = enConverter.parse();
|
||||
} else {
|
||||
// "with ({});" needed to fix default namespace scope issue
|
||||
// See https://bugzilla.mozilla.org/show_bug.cgi?id=330572
|
||||
default xml namespace = "http://purl.org/net/xbiblio/csl"; with ({});
|
||||
|
||||
if(this.source) {
|
||||
// parent/child
|
||||
var formatCSL = Zotero.Styles.get(this.source);
|
||||
if(!formatCSL) {
|
||||
throw(new Error('Style references '+this.source+', but this style is not installed',
|
||||
Zotero.Styles.ios.newFileURI(this.file).spec, null));
|
||||
}
|
||||
var file = formatCSL.file;
|
||||
} else {
|
||||
var file = this.file;
|
||||
}
|
||||
|
||||
var cslString = Zotero.File.getContents(file);
|
||||
var xml = new XML(Zotero.CSL.Global.cleanXML(cslString));
|
||||
}
|
||||
|
||||
return (Zotero.Styles.lastCSL = new Zotero.CSL(xml));
|
||||
});
|
||||
|
||||
Zotero.Style.prototype.__defineGetter__("class",
|
||||
/**
|
||||
* Retrieves the style class, either from the metadata that's already loaded or by loading the file
|
||||
* @type String
|
||||
*/
|
||||
function() {
|
||||
if(this._class) return this._class;
|
||||
return (this._class = this.csl.class);
|
||||
});
|
||||
|
||||
/**
|
||||
* Deletes a style
|
||||
*/
|
||||
Zotero.Style.prototype.delete = function() {
|
||||
// make sure no styles depend on this one
|
||||
var dependentStyles = false;
|
||||
var styles = Zotero.Styles.getAll();
|
||||
for each(var style in styles) {
|
||||
if(style.source == this.styleID) {
|
||||
dependentStyles = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(dependentStyles) {
|
||||
// copy dependent styles to hidden directory
|
||||
var hiddenDir = Zotero.getStylesDirectory();
|
||||
hiddenDir.append("hidden");
|
||||
Zotero.File.createDirectoryIfMissing(hiddenDir);
|
||||
this.file.moveTo(hiddenDir, null);
|
||||
} else {
|
||||
// remove defunct files
|
||||
this.file.remove(false);
|
||||
}
|
||||
|
||||
// check to see if this style depended on a hidden one
|
||||
if(this.source) {
|
||||
var source = Zotero.Styles.get(this.source);
|
||||
if(source && source.hidden) {
|
||||
var deleteSource = true;
|
||||
|
||||
// check to see if any other styles depend on the hidden one
|
||||
for each(var style in Zotero.Styles.getAll()) {
|
||||
if(style.source == this.source && style.styleID != this.styleID) {
|
||||
deleteSource = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// if it was only this style with the dependency, delete the source
|
||||
if(deleteSource) {
|
||||
source.delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Zotero.Styles.init();
|
||||
}
|
|
@ -275,9 +275,8 @@ var Zotero = new function(){
|
|||
Zotero.Sync.Runner.init();
|
||||
Zotero.Sync.Storage.init();
|
||||
|
||||
Zotero.MIMETypeHandler.init();
|
||||
Zotero.Proxies.init();
|
||||
Zotero.Ingester.MIMEHandler.init();
|
||||
Zotero.Styles.MIMEHandler.init();
|
||||
|
||||
this.initialized = true;
|
||||
Zotero.debug("Initialized in "+((new Date()).getTime() - start)+" ms");
|
||||
|
|
|
@ -495,12 +495,12 @@ integration.deleteCitedItem.title = Are you sure you want to remove this referen
|
|||
integration.deleteCitedItem.body = This reference is cited in the text of your document. Deleting it will remove all citations.
|
||||
|
||||
styles.installStyleURI = Install style "%1$S" from %2$S?
|
||||
styles.installStyle = Install style "%1$S"?
|
||||
styles.updateStyleURI = Update existing style "%1$S" with "%2$S" from %3$S?
|
||||
styles.updateStyle = Update existing style "%1$S" with "%2$S"?
|
||||
styles.installed = The style "%S" was installed successfully.
|
||||
styles.installError = %S does not appear to be a valid CSL file.
|
||||
styles.installErrorURI = %S does not appear to be a valid CSL file.
|
||||
styles.installSourceErrorURI = %1$S references an invalid or non-existent CSL file at %2$S as its source.
|
||||
styles.deleteStyle = Are you sure you want to delete the style "%1$S"?
|
||||
styles.deleteStyles = Are you sure you want to delete the selected styles?
|
||||
|
||||
sync.storage.kbRemaining = %SKB remaining
|
||||
sync.storage.none = None
|
||||
|
|
|
@ -164,6 +164,11 @@ grid row hbox:first-child
|
|||
-moz-box-align: center;
|
||||
}
|
||||
|
||||
#styleManager-csl
|
||||
{
|
||||
width: 36px;
|
||||
}
|
||||
|
||||
#zotero-prefpane-keys textbox
|
||||
{
|
||||
margin-left: -1px;
|
||||
|
|
|
@ -18,8 +18,8 @@ var xpcomFiles = [
|
|||
'zotero',
|
||||
'annotate',
|
||||
'attachments',
|
||||
'cite',
|
||||
'collectionTreeView',
|
||||
'csl',
|
||||
'dataServer',
|
||||
'data_access',
|
||||
'data/dataObjects',
|
||||
|
@ -43,6 +43,7 @@ var xpcomFiles = [
|
|||
'integration',
|
||||
'itemTreeView',
|
||||
'mime',
|
||||
'mimeTypeHandler',
|
||||
'notifier',
|
||||
'progressWindow',
|
||||
'proxy',
|
||||
|
@ -50,6 +51,7 @@ var xpcomFiles = [
|
|||
'report',
|
||||
'schema',
|
||||
'search',
|
||||
'style',
|
||||
'sync',
|
||||
'storage',
|
||||
'timeline',
|
||||
|
|
Loading…
Add table
Reference in a new issue