diff --git a/chrome/content/zotero/xpcom/translate.js b/chrome/content/zotero/xpcom/translate.js deleted file mode 100644 index 5bc6aae70a..0000000000 --- a/chrome/content/zotero/xpcom/translate.js +++ /dev/null @@ -1,3205 +0,0 @@ -/* - ***** BEGIN LICENSE BLOCK ***** - - Copyright © 2009 Center for History and New Media - George Mason University, Fairfax, Virginia, USA - http://zotero.org - - This file is part of Zotero. - - Zotero is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Zotero is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with Zotero. If not, see . - - ***** END LICENSE BLOCK ***** -*/ - -// Enumeration of types of translators -const TRANSLATOR_TYPES = {"import":1, "export":2, "web":4, "search":8}; - -// Byte order marks for various character sets -const BOMs = { - "UTF-8":"\xEF\xBB\xBF", - "UTF-16BE":"\xFE\xFF", - "UTF-16LE":"\xFF\xFE", - "UTF-32BE":"\x00\x00\xFE\xFF", - "UTF-32LE":"\xFF\xFE\x00\x00" -} - -/** - * Singleton to handle loading and caching of translators - * @namespace - */ -Zotero.Translators = new function() { - var _cache, _translators; - var _initialized = false; - - /** - * Initializes translator cache, loading all relevant translators into memory - */ - this.init = function() { - _initialized = true; - - var start = (new Date()).getTime(); - var transactionStarted = false; - - Zotero.UnresponsiveScriptIndicator.disable(); - - // Use try/finally so that we always reset the unresponsive script warning - try { - _cache = {"import":[], "export":[], "web":[], "search":[]}; - _translators = {}; - - var dbCacheResults = Zotero.DB.query("SELECT leafName, translatorJSON, "+ - "code, lastModifiedTime FROM translatorCache"); - var dbCache = {}; - for each(var cacheEntry in dbCacheResults) { - dbCache[cacheEntry.leafName] = cacheEntry; - } - - var i = 0; - var filesInCache = {}; - var contents = Zotero.getTranslatorsDirectory().directoryEntries; - while(contents.hasMoreElements()) { - var file = contents.getNext().QueryInterface(Components.interfaces.nsIFile); - var leafName = file.leafName; - if(!leafName || leafName[0] == ".") continue; - var lastModifiedTime = file.lastModifiedTime; - - var dbCacheEntry = false; - if(dbCache[leafName]) { - filesInCache[leafName] = true; - if(dbCache[leafName].lastModifiedTime == lastModifiedTime) { - dbCacheEntry = dbCache[file.leafName]; - } - } - - if(dbCacheEntry) { - // get JSON from cache if possible - var translator = new Zotero.Translator(file, dbCacheEntry.translatorJSON, dbCacheEntry.code); - filesInCache[leafName] = true; - } else { - // otherwise, load from file - var translator = new Zotero.Translator(file); - } - - if(translator.translatorID) { - if(_translators[translator.translatorID]) { - // same translator is already cached - translator.logError('Translator with ID '+ - translator.translatorID+' already loaded from "'+ - _translators[translator.translatorID].file.leafName+'"'); - } else { - // add to cache - _translators[translator.translatorID] = translator; - for(var type in TRANSLATOR_TYPES) { - if(translator.translatorType & TRANSLATOR_TYPES[type]) { - _cache[type].push(translator); - } - } - - if(!dbCacheEntry) { - // Add cache misses to DB - if(!transactionStarted) { - transactionStarted = true; - Zotero.DB.beginTransaction(); - } - Zotero.Translators.cacheInDB(leafName, translator.metadataString, translator.cacheCode ? translator.code : null, lastModifiedTime); - delete translator.metadataString; - } - } - } - - i++; - } - - // Remove translators from DB as necessary - for(var leafName in dbCache) { - if(!filesInCache[leafName]) { - Zotero.DB.query("DELETE FROM translatorCache WHERE leafName = ?", [leafName]); - } - } - - // Close transaction - if(transactionStarted) { - Zotero.DB.commitTransaction(); - } - - // Sort by priority - var collation = Zotero.getLocaleCollation(); - var cmp = function (a, b) { - if (a.priority > b.priority) { - return 1; - } - else if (a.priority < b.priority) { - return -1; - } - return collation.compareString(1, a.label, b.label); - } - for(var type in _cache) { - _cache[type].sort(cmp); - } - } - finally { - Zotero.UnresponsiveScriptIndicator.enable(); - } - - Zotero.debug("Cached "+i+" translators in "+((new Date()).getTime() - start)+" ms"); - } - - /** - * Gets the translator that corresponds to a given ID - */ - this.get = function(id) { - if(!_initialized) this.init(); - return _translators[id] ? _translators[id] : false; - } - - /** - * Gets all translators for a specific type of translation - */ - this.getAllForType = function(type) { - if(!_initialized) this.init(); - return _cache[type].slice(0); - } - - /** - * @param {String} label - * @return {String} - */ - this.getFileNameFromLabel = function(label) { - return Zotero.File.getValidFileName(label) + ".js"; - } - - - /** - * @param {String} metadata - * @param {String} metadata.translatorID Translator GUID - * @param {Integer} metadata.translatorType See TRANSLATOR_TYPES in translate.js - * @param {String} metadata.label Translator title - * @param {String} metadata.creator Translator author - * @param {String|Null} metadata.target Target regexp - * @param {String|Null} metadata.minVersion - * @param {String} metadata.maxVersion - * @param {Integer} metadata.priority - * @param {Boolean} metadata.inRepository - * @param {String} metadata.lastUpdated SQL date - * @param {String} code - * @return {nsIFile} - */ - this.save = function(metadata, code) { - if (!metadata.translatorID) { - throw ("metadata.translatorID not provided in Zotero.Translators.save()"); - } - - if (!metadata.translatorType) { - var found = false; - for each(var type in TRANSLATOR_TYPES) { - if (metadata.translatorType & type) { - found = true; - break; - } - } - if (!found) { - throw ("Invalid translatorType '" + metadata.translatorType + "' in Zotero.Translators.save()"); - } - } - - if (!metadata.label) { - throw ("metadata.label not provided in Zotero.Translators.save()"); - } - - if (!metadata.priority) { - throw ("metadata.priority not provided in Zotero.Translators.save()"); - } - - if (!metadata.lastUpdated) { - throw ("metadata.lastUpdated not provided in Zotero.Translators.save()"); - } - - if (!code) { - throw ("code not provided in Zotero.Translators.save()"); - } - - var fileName = Zotero.Translators.getFileNameFromLabel(metadata.label); - var destFile = Zotero.getTranslatorsDirectory(); - destFile.append(fileName); - - var metadataJSON; - - // JSON.stringify (FF 3.5.4 and up) has the benefit of indenting JSON - if (typeof JSON != "undefined" && 'function' == typeof JSON.stringify) { - metadataJSON = JSON.stringify(metadata,null,8); - } else { - var nsIJSON = Cc["@mozilla.org/dom/json;1"].createInstance(Ci.nsIJSON); - metadataJSON = nsIJSON.encode(metadata); - } - - var str = metadataJSON + "\n\n" + code; - - var translator = Zotero.Translators.get(metadata.translatorID); - if (translator && destFile.equals(translator.file)) { - var sameFile = true; - } - - if (!sameFile && destFile.exists()) { - var msg = "Overwriting translator with same filename '" - + fileName + "'"; - Zotero.debug(msg, 1); - Zotero.debug(metadata, 1); - Components.utils.reportError(msg + " in Zotero.Translators.save()"); - } - - if (translator && translator.file.exists()) { - translator.file.remove(false); - } - - Zotero.debug("Saving translator '" + metadata.label + "'"); - Zotero.debug(str); - Zotero.File.putContents(destFile, str); - - return destFile; - } - - this.cacheInDB = function(fileName, metadataJSON, code, lastModifiedTime) { - Zotero.DB.query("REPLACE INTO translatorCache VALUES (?, ?, ?, ?)", - [fileName, metadataJSON, code, lastModifiedTime]); - } -} - -/** - * @class Represents an individual translator - * @constructor - * @param {nsIFile} file File from which to generate a translator object - * @property {String} translatorID Unique GUID of the translator - * @property {Integer} translatorType Type of the translator (use bitwise & with TRANSLATOR_TYPES to read) - * @property {String} label Human-readable name of the translator - * @property {String} creator Author(s) of the translator - * @property {String} target Location that the translator processes - * @property {String} minVersion Minimum Zotero version - * @property {String} maxVersion Minimum Zotero version - * @property {Integer} priority Lower-priority translators will be selected first - * @property {Boolean} inRepository Whether the translator may be found in the repository - * @property {String} lastUpdated SQL-style date and time of translator's last update - * @property {String} code The executable JavaScript for the translator - */ -Zotero.Translator = function(file, json, code) { - const codeGetterFunction = function() { return Zotero.File.getContents(this.file); } - // Maximum length for the info JSON in a translator - const MAX_INFO_LENGTH = 4096; - const infoRe = /{(?:(?:"(?:[^"\r\n]*(?:\\")?)*")*[^}"]*)*}/; - - this.file = file; - - if(json) { - var info = Zotero.JSON.unserialize(json); - } else { - var fStream = Components.classes["@mozilla.org/network/file-input-stream;1"]. - createInstance(Components.interfaces.nsIFileInputStream); - var cStream = Components.classes["@mozilla.org/intl/converter-input-stream;1"]. - createInstance(Components.interfaces.nsIConverterInputStream); - fStream.init(file, -1, -1, 0); - cStream.init(fStream, "UTF-8", 8192, - Components.interfaces.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER); - - var str = {}; - cStream.readString(MAX_INFO_LENGTH, str); - - // We assume lastUpdated is at the end to avoid running the regexp on more than necessary - var lastUpdatedIndex = str.value.indexOf('"lastUpdated"'); - if (lastUpdatedIndex == -1) { - this.logError("Invalid or missing translator metadata JSON object in " + file.leafName); - fStream.close(); - return; - } - - // Add 50 characters to clear lastUpdated timestamp and final "}" - var header = str.value.substr(0, lastUpdatedIndex + 50); - var m = infoRe.exec(header); - if (!m) { - this.logError("Invalid or missing translator metadata JSON object in " + file.leafName); - fStream.close(); - return; - } - - this.metadataString = m[0]; - - try { - var info = Zotero.JSON.unserialize(this.metadataString); - } catch(e) { - this.logError("Invalid or missing translator metadata JSON object in " + file.leafName); - fStream.close(); - return; - } - } - - var haveMetadata = true; - // make sure we have all the properties - for each(var property in ["translatorID", "translatorType", "label", "creator", "target", "minVersion", "maxVersion", "priority", "lastUpdated", "inRepository"]) { - if(info[property] === undefined) { - this.logError('Missing property "'+property+'" in translator metadata JSON object in ' + file.leafName); - haveMetadata = false; - break; - } else { - this[property] = info[property]; - } - } - if(!haveMetadata) { - fStream.close(); - return; - } - - this.detectXPath = info["detectXPath"] ? info["detectXPath"] : null; - - /** - * g = Gecko (Firefox) - * c = Google Chrome (WebKit & V8) - * s = Safari (WebKit & Nitro/Squirrelfish Extreme) - * i = Internet Explorer - * a = All - */ - this.browserSupport = info["browserSupport"] ? info["browserSupport"] : "g"; - - if(this.translatorType & TRANSLATOR_TYPES["import"]) { - // compile import regexp to match only file extension - this.importRegexp = this.target ? new RegExp("\\."+this.target+"$", "i") : null; - } - - this.cacheCode = false; - if(this.translatorType & TRANSLATOR_TYPES["web"]) { - // compile web regexp - this.webRegexp = this.target ? new RegExp(this.target, "i") : null; - - if(!this.target) { - this.cacheCode = true; - - if(json) { - // if have JSON, also have code - this.code = code; - } else { - // for translators used on every page, cache code in memory - var strs = [str.value]; - var amountRead; - while(amountRead = cStream.readString(8192, str)) strs.push(str.value); - this.code = strs.join(""); - } - } - } - - if(!this.cacheCode) this.__defineGetter__("code", codeGetterFunction); - if(!json) cStream.close(); -} - -/** - * Log a translator-related error - * @param {String} message The error message - * @param {String} [type] The error type ("error", "warning", "exception", or "strict") - * @param {String} [line] The text of the line on which the error occurred - * @param {Integer} lineNumber - * @param {Integer} colNumber - */ -Zotero.Translator.prototype.logError = function(message, type, line, lineNumber, colNumber) { - var ios = Components.classes["@mozilla.org/network/io-service;1"]. - getService(Components.interfaces.nsIIOService); - Zotero.log(message, type ? type : "error", ios.newFileURI(this.file).spec); -} - - -/* - * Zotero.Translate: a class for translation of Zotero metadata from and to - * other formats - * - * type can be: - * export - * import - * web - * search - * - * a typical export process: - * var translatorObj = new Zotero.Translate(); - * var possibleTranslators = translatorObj.getTranslators(); - * // do something involving nsIFilePicker; remember, each possibleTranslator - * // object has properties translatorID, label, and targetID - * translatorObj.setLocation(myNsILocalFile); - * translatorObj.setTranslator(possibleTranslators[x]); // also accepts only an ID - * translatorObj.setHandler("done", _translationDone); - * translatorObj.translate(); - * - * - * PUBLIC PROPERTIES: - * - * type - the text type of translator (set by constructor, should be read only) - * document - the document object to be used for web scraping (read-only; set - * with setDocument) - * translator - the translator currently in use (read-only; set with - * setTranslator) - * location - the location of the target (read-only; set with setLocation) - * for import/export - this is an instance of nsILocalFile - * for web - this is a URL - * search - item (in toArray() format) to extrapolate data for (read-only; set - * with setSearch). - * items - items (in Zotero.Item format) to be exported. if this is empty, - * Zotero will export all items in the library (read-only; set with - * setItems). setting items disables export of collections. - * path - the path to the target; for web, this is the same as location - * string - the string content to be used as a file. - * libraryID - libraryID (e.g., of a group) of saved database items. null for local items, - * or false not to save. defaults to null; set using second argument of constructor. - * newItems - items created when translate() was called - * newCollections - collections created when translate() was called - * - * PSEUDO-PRIVATE PROPERTIES (used only by other objects in this file): - * - * waitForCompletion - whether to wait for asynchronous completion, or return - * immediately when script has finished executing - * configOptions - options set by translator modifying behavior of - * Zotero.Translate - * displayOptions - options available to user for this specific translator - * - * PRIVATE PROPERTIES: - * - * _charset - character set - * _numericTypes - possible numeric types as a comma-delimited string - * _handlers - handlers for various events (see setHandler) - * _sandbox - sandbox in which translators will be executed - * _streams - streams that need to be closed when execution is complete - * _IDMap - a map from IDs as specified in Zotero.Item() to IDs of actual items - * _parentTranslator - set when a translator is called from another translator. - * among other things, disables passing of the translate - * object to handlers and modifies complete() function on - * returned items - * _storage - the stored string to be treated as input - * _storageLength - the length of the stored string - * _exportFileDirectory - the directory to which files will be exported - * - * WEB-ONLY PROPERTIES: - * - * locationIsProxied - whether the URL being scraped is going through - * an EZProxy - * _downloadAssociatedFiles - whether to download content, according to - * preferences - * - * EXPORT-ONLY PROPERTIES: - * - * output - export output (if no location has been specified) - */ -Zotero.Translate = function(type) { - if (arguments.length > 1) { - throw ("Zotero.Translate only takes one parameter"); - } - - this.type = type; - - // import = 0001 = 1 - // export = 0010 = 2 - // web = 0100 = 4 - // search = 1000 = 8 - - // combination types determined by addition or bitwise AND - // i.e., import+export = 1+2 = 3 - this._numericTypes = ""; - for(var i=0; i<=1; i++) { - for(var j=0; j<=1; j++) { - for(var k=0; k<=1; k++) { - if(type == "import") { - this._numericTypes += ","+parseInt(i.toString()+j.toString()+k.toString()+"1", 2); - } else if(type == "export") { - this._numericTypes += ","+parseInt(i.toString()+j.toString()+"1"+k.toString(), 2); - } else if(type == "web") { - this._numericTypes += ","+parseInt(i.toString()+"1"+j.toString()+k.toString(), 2); - } else if(type == "search") { - this._numericTypes += ","+parseInt("1"+i.toString()+j.toString()+k.toString(), 2); - } else { - throw("invalid import type"); - } - } - } - } - this._numericTypes = this._numericTypes.substr(1); - - this._handlers = new Array(); - this._streams = new Array(); -} - -/* - * sets the browser to be used for web translation; also sets the location - */ -Zotero.Translate.prototype.setDocument = function(doc) { - this.document = doc; - this.setLocation(doc.location.href); -} - -/* - * sets the item to be used for searching - */ -Zotero.Translate.prototype.setSearch = function(search) { - this.search = search; -} - -/* - * sets the items to be used for export - */ -Zotero.Translate.prototype.setItems = function(items) { - this.items = items; -} - -/* - * Sets a Zotero.Connector.CookieManager to handle cookie management for XHRs initiated from this - * translate instance - * - * @param {Zotero.Connector.CookieManager} cookieManager - */ -Zotero.Translate.prototype.setCookieManager = function(cookieManager) { - this.cookieManager = cookieManager; -} - -/* - * sets the collection to be used for export (overrides setItems) - */ -Zotero.Translate.prototype.setCollection = function(collection) { - this.collection = collection; -} - -/* - * sets the location to operate upon (file should be an nsILocalFile object or - * web address) - */ -Zotero.Translate.prototype.setLocation = function(location) { - if(this.type == "web") { - // account for proxies - this.location = Zotero.Proxies.proxyToProper(location); - if(this.location != location) { - // figure out if this URL is being proxies - this.locationIsProxied = true; - } - this.path = this.location; - } else { - this.location = location; - if(this.location instanceof Components.interfaces.nsIFile) { // if a file - this.path = location.path; - } else { // if a url - this.path = location; - } - } -} - -/* - * sets the string to be used as a file - */ -Zotero.Translate.prototype.setString = function(string) { - this._storage = string; - this._storageLength = string.length; - this._storagePointer = 0; -} - -/* - * sets translator display options. you can also pass a translator (not ID) to - * setTranslator that includes a displayOptions argument - */ -Zotero.Translate.prototype.setDisplayOptions = function(displayOptions) { - this._setDisplayOptions = displayOptions; -} - -/* - * sets the translator to be used for import/export - * - * accepts either the object from getTranslators() or an ID - */ -Zotero.Translate.prototype.setTranslator = function(translator) { - if(!translator) { - throw("cannot set translator: invalid value"); - } - - this.translator = null; - this._setDisplayOptions = null; - - if(typeof(translator) == "object") { // passed an object and not an ID - if(translator.translatorID) { - if(translator.displayOptions) { - this._setDisplayOptions = translator.displayOptions; - } - - this.translator = [translator]; - } else { - // we have an array of translators - if(this.type != "search") { - throw("cannot set translator: a single translator must be specified when doing "+this.type+" translation"); - } - - // accept a list of objects - this.translator = []; - for(var i in translator) { - if(typeof(translator[i]) == "object") { - this.translator.push(translator[i]); - } else { - this.translator.push(Zotero.Translators.get(translator[i])); - } - } - } - } else { - this.translator = [Zotero.Translators.get(translator)]; - } - - return !!this.translator; -} - -/* - * registers a handler function to be called when translation is complete - * - * as the first argument, all handlers will be passed the current function. the - * second argument is dependent on the handler. - * - * select - * valid: web - * called: when the user needs to select from a list of available items - * passed: an associative array in the form id => text - * returns: a numerically indexed array of ids, as extracted from the passed - * string - * - * itemDone - * valid: import, web, search - * called: when an item has been processed; may be called asynchronously - * passed: an item object (see Zotero.Item) - * returns: N/A - * - * collectionDone - * valid: import - * called: when a collection has been processed, after all items have been - * added; may be called asynchronously - * passed: a collection object (see Zotero.Collection) - * returns: N/A - * - * done - * valid: all - * called: when all processing is finished - * passed: true if successful, false if an error occurred - * returns: N/A - * - * debug - * valid: all - * called: when Zotero.debug() is called - * passed: string debug message - * returns: true if message should be logged to the console, false if not - * - * error - * valid: all - * called: when a fatal error occurs - * passed: error object (or string) - * returns: N/A - * - * translators - * valid: all - * called: when a translator search initiated with Zotero.getTranslators() is - * complete - * passed: an array of appropriate translators - * returns: N/A - */ -Zotero.Translate.prototype.setHandler = function(type, handler) { - if(!this._handlers[type]) { - this._handlers[type] = new Array(); - } - this._handlers[type].push(handler); -} - -/* - * clears all handlers for a given function - */ -Zotero.Translate.prototype.clearHandlers = function(type) { - this._handlers[type] = new Array(); -} - -/* - * calls a handler (see setHandler above) - */ -Zotero.Translate.prototype.runHandler = function(type, argument) { - var returnValue = undefined; - if(this._handlers[type]) { - for(var i in this._handlers[type]) { - Zotero.debug("Translate: running handler "+i+" for "+type, 5); - try { - if(this._parentTranslator) { - returnValue = this._handlers[type][i](null, argument); - } else { - returnValue = this._handlers[type][i](this, argument); - } - } catch(e) { - if(this._parentTranslator) { - // throw handler errors if they occur when a translator is - // called from another translator, so that the - // "Could Not Translate" dialog will appear if necessary - throw(e); - } else { - // otherwise, fail silently, so as not to interfere with - // interface cleanup - Zotero.debug("Translate: "+e+' in handler '+i+' for '+type, 5); - } - } - } - } - return returnValue; -} - -/* - * gets all applicable translators - * - * for import, you should call this after setLocation; otherwise, you'll just get - * a list of all import filters, not filters equipped to handle a specific file - * - * this returns a list of translator objects, of which the following fields - * are useful: - * - * translatorID - the GUID of the translator - * label - the name of the translator - * itemType - the type of item this scraper says it will scrape - */ -Zotero.Translate.prototype.getTranslators = function() { - // do not allow simultaneous instances of getTranslators - if(this._translatorSearch) this._translatorSearch.running = false; - - var translators = Zotero.Translators.getAllForType(this.type); - - // create a new sandbox - this._generateSandbox(); - this._setSandboxMode("detect"); - - var possibleTranslators = new Array(); - Zotero.debug("Translate: Searching for translators for "+(this.path ? this.path : "an undisclosed location"), 3); - - // see which translators can translate - this._translatorSearch = new Zotero.Translate.TranslatorSearch(this, translators); - - if(this._translatorSearch.asyncMode) { - // erroring should call complete - var me = this; - this.error = function(value, error) { me._translatorSearch.complete(value, error); }; - } else { - // return translators if synchronous - return this._translatorSearch.foundTranslators; - } -} - -/* - * loads a translator into a sandbox - */ -Zotero.Translate.prototype._loadTranslator = function() { - if(!this._sandbox || this.type == "search") { - // create a new sandbox if none exists, or for searching (so that it's - // bound to the correct url) - this._generateSandbox(); - } - this._setSandboxMode("translate"); - - // parse detect code for the translator - if(!this._parseCode(this.translator[0])) { - this._translationComplete(false, "parse error"); - return false; - } - - return true; -} - -/** - * does the actual translation - * - * @param {NULL|Integer|FALSE} [libraryID=null] Library in which to save items, - * or NULL for default library; - * if FALSE, don't save items - * @param {Boolean} [saveAttachments=true] Exclude attachments (e.g., snapshots) on import - */ -Zotero.Translate.prototype.translate = function(libraryID, saveAttachments) { - /* - * initialize properties - */ - this.newItems = new Array(); - this.newCollections = new Array(); - this._IDMap = new Array(); - this._complete = false; - this._itemsDone = false; - - if(!this.translator || !this.translator.length) { - throw("cannot translate: no translator specified"); - } - - if(!this.location && this.type != "search" && this.type != "export" && !this._storage) { - // searches operate differently, because we could have an array of - // translators and have to go through each - throw("cannot translate: no location specified"); - } - - if(libraryID === false) { - this.libraryID = false; - } - else if(libraryID === true || libraryID == undefined) { - this.libraryID = null; - } - else { - this.libraryID = libraryID; - } - this.saveAttachments = !(saveAttachments === false); - this.saveFiles = this.saveAttachments; - - // If group filesEditable==false, don't save attachments - if (typeof this.libraryID == 'number') { - var type = Zotero.Libraries.getType(this.libraryID); - switch (type) { - case 'group': - var groupID = Zotero.Groups.getGroupIDFromLibraryID(this.libraryID); - var group = Zotero.Groups.get(groupID); - if (!group.filesEditable) { - this.saveFiles = false; - } - break; - } - } - - // erroring should end - var me = this; - this.error = function(value, error) { me._translationComplete(value, error); } - - if(!this._loadTranslator()) { - return; - } - - if(this._setDisplayOptions) { - this.displayOptions = this._setDisplayOptions; - } - - if(this._storage) { - // enable reading from storage, which we can't do until the translator - // is loaded - this._storageFunctions(true); - } - - var returnValue; - if(this.type == "web") { - returnValue = this._web(); - } else if(this.type == "import") { - returnValue = this._import(); - } else if(this.type == "export") { - returnValue = this._export(); - } else if(this.type == "search") { - returnValue = this._search(); - } - - if(returnValue && !this.waitForCompletion) { - // if synchronous, call _translationComplete(); - this._translationComplete(true); - } -} - -/* - * parses translator detect code - */ -Zotero.Translate.prototype._parseCode = function(translator) { - this.configOptions = {}; - this.displayOptions = {}; - - Zotero.debug("Translate: Parsing code for "+translator.label, 4); - - try { - Components.utils.evalInSandbox("var translatorInfo = "+translator.code, this._sandbox); - return true; - } catch(e) { - if('function' == typeof translator.logError) { - translator.logError(e.toString()); - } else { - Components.utils.reportError(e); - } - this._debug(e+' in parsing code for '+translator.label, 3); - return false; - } -} - -/* - * generates a sandbox for scraping/scraper detection - */ -Zotero.Translate.prototype._generateSandbox = function() { - var me = this; - - // get sandbox URL - var sandboxLocation = "http://www.example.com/"; - if(this.type == "web") { - // use real URL, not proxied version, to create sandbox - var sandboxLocation = this.document.defaultView; - } else if(this.type == "search") { - // generate sandbox for search by extracting domain from translator target - if(this.translator && this.translator[0] && this.translator[0].target) { - // so that web translators work too - const searchSandboxRe = /^http:\/\/[\w.]+\//; - var tempURL = this.translator[0].target.replace(/\\/g, "").replace(/\^/g, ""); - var m = searchSandboxRe.exec(tempURL); - if(m) sandboxLocation = m[0]; - } - } else if(this._sandboxLocation) { - sandboxLocation = this._sandboxLocation; - } - Zotero.debug("Translate: Binding sandbox to "+(typeof sandboxLocation == "object" ? sandboxLocation.document.location : sandboxLocation), 4); - - // set up sandbox - this._sandbox = new Components.utils.Sandbox(sandboxLocation); - Components.utils.evalInSandbox("var Zotero = {};"+ - "Zotero.Item = function (itemType) {"+ - "this.itemType = itemType;"+ - "this.creators = [];"+ - "this.notes = [];"+ - "this.tags = [];"+ - "this.seeAlso = [];"+ - "this.attachments = [];"+ - "};"+ - "Zotero.Item.prototype.complete = function() { Zotero._itemDone(this); };", this._sandbox) - - // add utilities - this._sandbox.Zotero.Utilities = new Zotero.Utilities.Translate(this); - this._sandbox.Zotero.HTTP = this._sandbox.Zotero.Utilities; - - if(this.type == "export") { - // add routines to retrieve items and collections - this._sandbox.Zotero.nextItem = function() { return me._exportGetItem() }; - this._sandbox.Zotero.nextCollection = function() { return me._exportGetCollection() } - } else { - // copy routines to add new items - this._sandbox.Zotero._itemDone = function(a) {me._itemDone(a)}; - - if(this.type == "import") { - // add routines to add new collections - this._sandbox.Zotero.Collection = Zotero.Translate.GenerateZoteroCollectionClass(); - // attach the function to be run when a collection is done - this._sandbox.Zotero.Collection.prototype.complete = function() {me._collectionDone(this)}; - } else if(this.type == "web" || this.type == "search") { - // set up selectItems handler - this._sandbox.Zotero.selectItems = function(options) { return me._selectItems(options) }; - } - } - - this._sandbox.XPathResult = Components.interfaces.nsIDOMXPathResult; - - // for debug messages - this._sandbox.Zotero.debug = function(string) {me._debug(string, 4)}; - - // for adding configuration options - this._sandbox.Zotero.configure = function(option, value) {me._configure(option, value) }; - // for adding displayed options - this._sandbox.Zotero.addOption = function(option, value) {me._addOption(option, value) }; - // for getting the value of displayed options - this._sandbox.Zotero.getOption = function(option) { return me._getOption(option) }; - - // for loading other translators and accessing their methods - this._sandbox.Zotero.loadTranslator = function(type) { - if(typeof type !== "string") { - Zotero.debug("loadTranslator: type must be a string"); - return; - } - - var translation = new Zotero.Translate(type); - translation._parentTranslator = me; - translation._sandboxLocation = sandboxLocation; - - if(type == "export" && (me.type == "web" || me.type == "search")) { - throw("for security reasons, web and search translators may not call export translators"); - } - - // for security reasons, safeTranslator wraps the translator object. - // note that setLocation() is not allowed - var safeTranslator = new Object(); - safeTranslator.setSearch = function(arg) { return translation.setSearch(arg) }; - safeTranslator.setDocument = function(arg) { return translation.setDocument(arg) }; - safeTranslator.setHandler = function(arg1, arg2) { - translation.setHandler(arg1, - function(obj, item) { - try { - if(Zotero.isFx4 && (type == "web" || type == "search")) { - // item is wrapped in an XPCCrossOriginWrapper that we can't get rid of - // except by making a deep copy. seems to be due to - // https://bugzilla.mozilla.org/show_bug.cgi?id=580128 - // hear that? that's the sound of me banging my head against the wall. - // if there is no better way to do this soon, i am going to need a - // brain transplant... - var unwrappedItem = JSON.parse(JSON.stringify(item)); - unwrappedItem.complete = item.complete; - } else { - var unwrappedItem = item; - } - - arg2(obj, unwrappedItem); - } catch(e) { - me.error(false, e); - } - } - ); - }; - safeTranslator.setString = function(arg) { translation.setString(arg) }; - safeTranslator.setTranslator = function(arg) { return translation.setTranslator(arg) }; - safeTranslator.getTranslators = function() { return translation.getTranslators() }; - safeTranslator.translate = function() { - var noHandlers = true; - for(var i in translation._handlers) { - noHandlers = false; - break; - } - if(noHandlers) { - if(type != "export") { - translation.setHandler("itemDone", function(obj, item) { item.complete() }); - } - if(type == "web") { - translation.setHandler("selectItems", me._handlers["selectItems"]); - } - } - return translation.translate(false); - }; - safeTranslator.getTranslatorObject = function() { - // load the translator into our sandbox - translation._loadTranslator(); - // initialize internal IO - translation._initializeInternalIO(); - - var noHandlers = true; - for(var i in translation._handlers) { - noHandlers = false; - break; - } - if(noHandlers) { - if(type != "export") { - translation.setHandler("itemDone", function(obj, item) { item.complete() }); - } - if(type == "web") { - translation.setHandler("selectItems", me._handlers["selectItems"]); - } - } - - // return sandbox - return translation._sandbox; - }; - - return safeTranslator; - } -} - -/* - * Adds appropriate methods for detect/translate modes - */ -Zotero.Translate.prototype._setSandboxMode = function(mode) { - var me = this; - - // erase waitForCompletion status and done function - this.waitForCompletion = false; - this._sandbox.Zotero.done = undefined; - - if(mode == "detect") { - // for asynchronous operation, use wait() - // done() is implemented after wait() is called - this._sandbox.Zotero.wait = function() { me._enableAsynchronousDetect() }; - } else { - // for asynchronous operation, use wait() - // done() is implemented after wait() is called - this._sandbox.Zotero.wait = function() { me._enableAsynchronousTranslate() }; - } -} - -/* - * sets an option that modifies the way the translator is executed - * - * called as configure() in translator detectCode - * - * current options: - * - * dataMode - * valid: import, export - * options: rdf, block, line - * purpose: selects whether write/read behave as standard text functions or - * use an RDF data source - * - * getCollections - * valid: export - * options: true, false - * purpose: selects whether export translator will receive an array of - * collections and children in addition to the array of items and - * children - */ -Zotero.Translate.prototype._configure = function(option, value) { - if(typeof option !== "string") { - Zotero.debug ("configure: option must be a string"); - return; - } - if(typeof value === "object" && value !== null) { - value = new XPCSafeJSObjectWrapper(value); - } - - this.configOptions[option] = value; - Zotero.debug("Translate: Setting configure option "+option+" to "+value, 4); -} - -/* - * adds translator options to be displayed in a dialog - * - * called as addOption() in detect code - * - * current options are exportNotes and exportFileData - */ -Zotero.Translate.prototype._addOption = function(option, value) { - if(typeof option !== "string") { - Zotero.debug ("addOption: option must be a string"); - return; - } - if(typeof value === "object" && value !== null) { - value = new XPCSafeJSObjectWrapper(value); - } - - this.displayOptions[option] = value; - Zotero.debug("Translate: Setting display option "+option+" to "+value, 4); -} - -/* - * gets translator options that were displayed in a dialog - * - * called as getOption() in detect code - * - */ -Zotero.Translate.prototype._getOption = function(option) { - if(typeof option !== "string") { - Zotero.debug ("getOption: option must be a string"); - return; - } - return this.displayOptions[option]; -} - -/* - * makes translation API wait until done() has been called from the translator - * before executing _translationComplete - * - * called as wait() in translator code - */ -Zotero.Translate.prototype._enableAsynchronousDetect = function() { - var me = this; - this.waitForCompletion = true; - this._sandbox.Zotero.done = function(arg) { - if(arg !== null && typeof arg === "object") { - Zotero.debug("done: argument must not be an object"); - me._translatorSearch.complete(false); - } else { - me._translatorSearch.complete(arg); - } - }; -} - -Zotero.Translate.prototype._enableAsynchronousTranslate = function() { - var me = this; - this.waitForCompletion = true; - this._sandbox.Zotero.done = function(val, error) { - if(val !== null && typeof val === "object") { - val = new XPCSafeJSObjectWrapper(val); - } - if(error !== null && typeof error === "object") { - error = new XPCSafeJSObjectWrapper(error); - } - me._translationComplete(val == undefined ? true : val, (error ? error : "")) - }; -} - -/* - * lets user pick which items s/he wants to put in his/her library - * - * called as selectItems() in translator code - */ -Zotero.Translate.prototype._selectItems = function(options) { - if(!options instanceof XPCSafeJSObjectWrapper) { - options = new XPCSafeJSObjectWrapper(options); - } - - // hack to see if there are options - var haveOptions = false; - for(var i in options) { - haveOptions = true; - break; - } - - if(!haveOptions) { - throw "translator called select items with no items"; - } - - if(this._handlers.select) { - return this.runHandler("select", options); - } else { // no handler defined; assume they want all of them - return options; - } -} - -/* - * executed on translator completion, either automatically from a synchronous - * scraper or as done() from an asynchronous scraper - * - * finishes things up and calls callback function(s) - */ -Zotero.Translate.prototype._translationComplete = function(returnValue, error) { - // to make sure this isn't called twice - if(returnValue === undefined) { - returnValue = this._itemsDone; - } - - if(!this._complete) { - this._complete = true; - - if(this.type == "search" && !this._itemsDone) { - // if we're performing a search and didn't get any results, go on - // to the next translator - Zotero.debug("Translate: Could not find a result using "+this.translator[0].label+": \n" - +this._generateErrorString(error), 3); - if(this.translator.length > 1) { - this.translator.shift(); - this.translate(this.libraryID, this.saveAttachments); - return; - } - - returnValue = false; - } else { - // close open streams - this._closeStreams(); - - if(!returnValue) { - // report error to console - if(typeof this.translator[0] != "undefined" && 'function' == typeof this.translator[0].logError) { - this.translator[0].logError(error.toString(), "exception"); - } else { - Components.utils.reportError(error); - } - - // report error to debug log - var errorString = this._generateErrorString(error); - this._debug("Translation using "+(this.translator && this.translator[0] && this.translator[0].label ? this.translator[0].label : "no translator")+" failed: \n"+errorString, 2); - - // report translation error for webpages to mothership - if(this.type == "web") this._reportTranslationFailure(errorString); - - this.runHandler("error", error); - } else { - this._debug("Translation successful"); - } - } - - // call handlers - this.runHandler("done", returnValue); - } -} - -/* - * generates a useful error string, for submitting and debugging purposes - */ -Zotero.Translate.prototype._generateErrorString = function(error) { - var errorString = ""; - if(typeof(error) == "string") { - errorString = "\nthrown exception => "+error; - } else { - for(var i in error) { - if(typeof(error[i]) != "object") { - errorString += "\n"+i+' => '+error[i]; - } - } - } - - errorString += "\nurl => "+this.path - + "\ndownloadAssociatedFiles => "+Zotero.Prefs.get("downloadAssociatedFiles") - + "\nautomaticSnapshots => "+Zotero.Prefs.get("automaticSnapshots"); - return errorString.substr(1); -} - -/* - * runs an HTTP request to report a translation error - */ -Zotero.Translate.prototype._reportTranslationFailure = function(errorData) { - if(this.translator[0].inRepository && Zotero.Prefs.get("reportTranslationFailure")) { - var postBody = "id=" + encodeURIComponent(this.translator[0].translatorID) + - "&lastUpdated=" + encodeURIComponent(this.translator[0].lastUpdated) + - "&diagnostic=" + encodeURIComponent(Zotero.getSystemInfo()) + - "&errorData=" + encodeURIComponent(errorData); - Zotero.HTTP.doPost("http://www.zotero.org/repo/report", postBody); - } -} - -/* - * closes open file streams, if any exist - */ -Zotero.Translate.prototype._closeStreams = function() { - // serialize RDF and unregister dataSource - if(this._rdf) { - if(this.type == "export") { - // initialize serializer and add prefixes - var serializer = Serializer(); - for(var prefix in this._rdf.namespaces) { - serializer.suggestPrefix(prefix, this._rdf.namespaces[prefix]); - } - - // serialize in appropriate format - if(this.configOptions.dataMode == "rdf/n3") { - var output = serializer.statementsToN3(this._rdf.statements); - } else { - var output = serializer.statementsToXML(this._rdf.statements); - } - - if(this._streams.length) { - Zotero.debug("writing to stream"); - // write serialized data to file - var intlStream = Components.classes["@mozilla.org/intl/converter-output-stream;1"] - .createInstance(Components.interfaces.nsIConverterOutputStream); - intlStream.init(this._streams[0], "UTF-8", 4096, "?".charCodeAt(0)); - this._streams.push(intlStream); - intlStream.writeString(output); - } else { - this.output = output; - } - } - - delete this._rdf; - } - - if(this._streams.length) { - for(var i in this._streams) { - var stream = this._streams[i]; - - // stream could be either an input stream or an output stream - try { - stream.QueryInterface(Components.interfaces.nsIFileInputStream); - } catch(e) { - try { - stream.QueryInterface(Components.interfaces.nsIFileOutputStream); - } catch(e) { - } - } - - // encase close in try block, because it's possible it's already - // closed - try { - stream.close(); - } catch(e) { - } - } - } - - delete this._streams; - this._streams = new Array(); - this._inputStream = null; -} - -/* - * handles tags and see also data for notes and attachments - */ -Zotero.Translate.prototype._itemTagsAndSeeAlso = function(item, newItem) { - // add to ID map - if(item.itemID) { - this._IDMap[item.itemID] = newItem.id; - } - // add see alsos - if(item.seeAlso) { - for each(var seeAlso in item.seeAlso) { - if(this._IDMap[seeAlso]) { - newItem.addRelatedItem(this._IDMap[seeAlso]); - } - } - newItem.save(); - } - if(item.tags) { - var tagsToAdd = {}; - tagsToAdd[0] = []; // user tags - tagsToAdd[1] = []; // automatic tags - - for each(var tag in item.tags) { - if(typeof(tag) == "string") { - // accept strings in tag array as automatic tags, or, if - // importing, as non-automatic tags - if (this.type == 'import') { - tagsToAdd[0].push(tag); - } - else { - tagsToAdd[1].push(tag); - } - } else if(typeof(tag) == "object") { - // also accept objects - if(tag.tag) { - if (!tagsToAdd[tag.type]) { - tagsToAdd[tag.type] = []; - } - tagsToAdd[tag.type].push(tag.tag); - } - } - } - - for (var type in tagsToAdd) { - if (tagsToAdd[type].length) { - newItem.addTags(tagsToAdd[type], type); - } - } - } -} - -/* - * executed when an item is done and ready to be loaded into the database - */ -Zotero.Translate._urlRe = /(([A-Za-z]+):\/\/[^\s]*)/i; -Zotero.Translate.prototype._itemDone = function(item, attachedTo) { - // warn if itemDone called after translation completed - if(this._complete) { - Zotero.debug("Translate: WARNING: Zotero.Item.complete() called after Zotero.done(); please fix your code", 2); - } - - // make item safe to access - if(!item instanceof XPCSafeJSObjectWrapper) { - item = new XPCSafeJSObjectWrapper(item); - } - - if(this.type == "web") { - // store library catalog if this item was captured from a website, and - // libraryCatalog is truly undefined (not false or "") - if(typeof item.repository != 'undefined') { - Zotero.debug("Translate: 'repository' field is now 'libraryCatalog'; please fix your code", 2); - item.libraryCatalog = item.repository; - delete item.repository; - } - - if(typeof item.libraryCatalog == 'undefined') { - item.libraryCatalog = this.translator[0].label; - } - } - - this._itemsDone = true; - - // if we're not supposed to save the item or we're in a child translator, - // just return the item array - if(this.libraryID === false || this._parentTranslator) { - - // if a parent sandbox exists, use complete() function from that sandbox - if(this._parentTranslator) { - var pt = this._parentTranslator; - item.complete = function() { pt._itemDone(this) }; - Zotero.debug("Translate: Calling done from parent sandbox", 4); - } - this.runHandler("itemDone", item); - return; - } - - // Get typeID, defaulting to "webpage" - var type = (item.itemType ? item.itemType : "webpage"); - - if(type == "note") { // handle notes differently - var newItem = new Zotero.Item('note'); - newItem.libraryID = this.libraryID ? this.libraryID : null; - newItem.setNote(item.note); - var myID = newItem.save(); - // re-retrieve the item - var newItem = Zotero.Items.get(myID); - } else { - if(!item.title && this.type == "web") { - throw("item has no title"); - } - - // if item was accessed through a proxy, ensure that the proxy - // address remains in the accessed version - if(this.locationIsProxied && item.url) { - item.url = Zotero.Proxies.properToProxy(item.url); - } - - // create new item - if(type == "attachment") { - if(this.type != "import") { - Zotero.debug("Translate: Discarding standalone attachment", 2); - return; - } - - Zotero.debug("Translate: Adding attachment", 4); - - if(!item.url && !item.path) { - Zotero.debug("Translate: Ignoring attachment: no path or URL specified", 2); - return; - } - - if(!item.path) { - // see if this is actually a file URL - var m = Zotero.Translate._urlRe.exec(item.url); - var protocol = m ? m[2].toLowerCase() : ""; - if(protocol == "file") { - item.path = item.url; - item.url = false; - } else if(protocol != "http" && protocol != "https") { - Zotero.debug("Translate: Unrecognized protocol "+protocol, 2); - return; - } - } - - if(!item.path) { - // create from URL - try { - var myID = Zotero.Attachments.linkFromURL(item.url, attachedTo, - (item.mimeType ? item.mimeType : undefined), - (item.title ? item.title : undefined)); - } catch(e) { - Zotero.debug("Translate: Error adding attachment "+item.url, 2); - return; - } - Zotero.debug("Translate: Created attachment; id is "+myID, 4); - var newItem = Zotero.Items.get(myID); - } else { - var uri, file; - - // generate nsIFile - var IOService = Components.classes["@mozilla.org/network/io-service;1"]. - getService(Components.interfaces.nsIIOService); - try { - var uri = IOService.newURI(item.path, "", null); - } - catch (e) { - var msg = "Error parsing attachment path: " + item.path; - Components.utils.reportError(msg); - Zotero.debug("Translate: " + msg, 2); - } - - if (uri) { - try { - var file = uri.QueryInterface(Components.interfaces.nsIFileURL).file; - if (file.path == '/') { - var msg = "Error parsing attachment path: " + item.path; - Components.utils.reportError(msg); - Zotero.debug("Translate: " + msg, 2); - } - } - catch (e) { - var msg = "Error getting file from attachment path: " + item.path; - Components.utils.reportError(msg); - Zotero.debug("Translate: " + msg, 2); - } - } - - if (!file || !file.exists()) { - // use item title if possible, or else file leaf name - var title = item.title; - if(!title) { - title = file ? file.leafName : ''; - } - - var myID = Zotero.Attachments.createMissingAttachment( - item.url ? Zotero.Attachments.LINK_MODE_IMPORTED_URL - : Zotero.Attachments.LINK_MODE_IMPORTED_FILE, - file, item.url ? item.url : null, title, - item.mimeType, item.charset, attachedTo); - } - else if (item.url) { - var myID = Zotero.Attachments.importSnapshotFromFile(file, - item.url, item.title, item.mimeType, item.charset, - attachedTo); - } - else { - var myID = Zotero.Attachments.importFromFile(file, attachedTo); - } - } - - var typeID = Zotero.ItemTypes.getID("attachment"); - var newItem = Zotero.Items.get(myID); - - // add note if necessary - if(item.note) { - newItem.setNote(item.note); - } - } else { - var typeID = Zotero.ItemTypes.getID(type); - var newItem = new Zotero.Item(typeID); - newItem.libraryID = this.libraryID ? this.libraryID : null; - } - - // makes looping through easier - item.itemType = item.complete = undefined; - - // automatically set access date if URL is set - if(item.url && typeof item.accessDate == 'undefined' && this.type == "web") { - item.accessDate = "CURRENT_TIMESTAMP"; - } - - var fieldID, data; - for(var field in item) { - // loop through item fields - data = item[field]; - - if(data) { // if field has content - if(field == "creators") { // creators are a special case - for(var j in data) { - // try to assign correct creator type - if(data[j].creatorType) { - try { - var creatorTypeID = Zotero.CreatorTypes.getID(data[j].creatorType); - } catch(e) { - Zotero.debug("Translate: Invalid creator type "+data[j].creatorType+" for creator index "+j, 2); - } - } - if(!creatorTypeID) { - var creatorTypeID = 1; - } - - // Single-field mode - if (data[j].fieldMode && data[j].fieldMode == 1) { - var fields = { - lastName: data[j].lastName, - fieldMode: 1 - }; - } - // Two-field mode - else { - var fields = { - firstName: data[j].firstName, - lastName: data[j].lastName - }; - } - - var creator = null; - var creatorDataID = Zotero.Creators.getDataID(fields); - if(creatorDataID) { - var linkedCreators = Zotero.Creators.getCreatorsWithData(creatorDataID, this.libraryID); - if (linkedCreators) { - // TODO: support identical creators via popup? ugh... - var creatorID = linkedCreators[0]; - creator = Zotero.Creators.get(creatorID); - } - } - if(!creator) { - creator = new Zotero.Creator; - creator.libraryID = this.libraryID ? this.libraryID : null; - creator.setFields(fields); - var creatorID = creator.save(); - } - - newItem.setCreator(j, creator, creatorTypeID); - } - } else if(field == "seeAlso") { - newItem.translateSeeAlso = data; - } else if(field != "note" && field != "notes" && field != "itemID" && - field != "attachments" && field != "tags" && - (fieldID = Zotero.ItemFields.getID(field))) { - // if field is in db - - // try to map from base field - Zotero.debug(Zotero.ItemFields.getFieldIDFromTypeAndBase(19, 7)); - if(Zotero.ItemFields.isBaseField(fieldID)) { - var fieldID = Zotero.ItemFields.getFieldIDFromTypeAndBase(typeID, fieldID); - if(fieldID) { - Zotero.debug("Translate: Mapping "+field+" to "+Zotero.ItemFields.getName(fieldID), 5); - } else { - } - } - Zotero.debug(fieldID); - - // if field is valid for this type, set field - Zotero.debug(Zotero.ItemFields.getName(Zotero.ItemFields.getID(field))); - Zotero.debug("Translate: testing "+fieldID+" "+typeID); - if(fieldID && Zotero.ItemFields.isValidForType(fieldID, typeID)) { - newItem.setField(fieldID, data); - } else { - Zotero.debug("Translate: Discarded field "+field+" for item: field not valid for type "+type, 3); - } - } - } - } - - // create short title - if(this.type == "web" && - item.shortTitle === undefined && - Zotero.ItemFields.isValidForType("shortTitle", typeID)) { - // get field id - var fieldID = Zotero.ItemFields.getFieldIDFromTypeAndBase(typeID, "title"); - // get title - var title = newItem.getField(fieldID); - - if(title) { - // only set if changes have been made - var set = false; - - // shorten to before first colon - var index = title.indexOf(":"); - if(index !== -1) { - title = title.substr(0, index); - set = true; - } - // shorten to after first question mark - index = title.indexOf("?"); - if(index !== -1) { - index++; - if(index != title.length) { - title = title.substr(0, index); - set = true; - } - } - - if(set) newItem.setField("shortTitle", title); - } - } - - // save item - if(myID) { - newItem.save(); - } else { - var myID = newItem.save(); - newItem = Zotero.Items.get(myID); - } - - // handle notes - if(item.notes) { - for each(var note in item.notes) { - var myNote = new Zotero.Item('note'); - myNote.libraryID = this.libraryID ? this.libraryID : null; - myNote.setNote(typeof note == "object" ? note.note : note); - if (myID) { - myNote.setSource(myID); - } - var noteID = myNote.save(); - - if(typeof note == "object") { - // handle see also - myNote = Zotero.Items.get(noteID); - this._itemTagsAndSeeAlso(note, myNote); - } - } - } - - var automaticSnapshots = Zotero.Prefs.get("automaticSnapshots"); - var downloadAssociatedFiles = Zotero.Prefs.get("downloadAssociatedFiles"); - - // handle attachments - if(item.attachments && this.saveAttachments && - // DEBUG: is "this.type == 'import'" still necessary with this.saveAttachments? - (this.type == 'import' || automaticSnapshots || downloadAssociatedFiles)) { - for each(var attachment in item.attachments) { - if(this.type == "web") { - if(!attachment.url && !attachment.document) { - Zotero.debug("Translate: Not adding attachment: no URL specified", 2); - } else { - if(attachment.snapshot === false) { - if(!automaticSnapshots) { - continue; - } - - // if snapshot is explicitly set to false, attach as link - if(attachment.document) { - Zotero.Attachments.linkFromURL(attachment.document.location.href, myID, - (attachment.mimeType ? attachment.mimeType : attachment.document.contentType), - (attachment.title ? attachment.title : attachment.document.title)); - } else { - if(!attachment.mimeType || !attachment.title) { - Zotero.debug("Translate: Either mimeType or title is missing; attaching file will be slower", 3); - } - - try { - Zotero.Attachments.linkFromURL(attachment.url, myID, - (attachment.mimeType ? attachment.mimeType : undefined), - (attachment.title ? attachment.title : undefined)); - } catch(e) { - Zotero.debug("Translate: Error adding attachment "+attachment.url, 2); - } - } - } else if(this.saveFiles && (attachment.document - || (attachment.mimeType && attachment.mimeType == "text/html") - || downloadAssociatedFiles)) { - - // if snapshot is not explicitly set to false, retrieve snapshot - if(attachment.document) { - if(automaticSnapshots) { - try { - Zotero.Attachments.importFromDocument(attachment.document, myID, attachment.title); - } catch(e) { - Zotero.debug("Translate: Error attaching document", 2); - } - } - // Save attachment if snapshot pref enabled or not HTML - // (in which case downloadAssociatedFiles applies) - } else if(this.saveFiles && (automaticSnapshots || !attachment.mimeType - || attachment.mimeType != "text/html")) { - var mimeType = null; - var title = null; - - if(attachment.mimeType) { - // first, try to extract mime type from mimeType attribute - mimeType = attachment.mimeType; - } else if(attachment.document && attachment.document.contentType) { - // if that fails, use document if possible - mimeType = attachment.document.contentType - } - - // same procedure for title as mime type - if(attachment.title) { - title = attachment.title; - } else if(attachment.document && attachment.document.title) { - title = attachment.document.title; - } - - if(this.locationIsProxied) { - attachment.url = Zotero.Proxies.properToProxy(attachment.url); - } - - var fileBaseName = Zotero.Attachments.getFileBaseNameFromItem(myID); - try { - Zotero.Attachments.importFromURL(attachment.url, myID, title, fileBaseName); - } catch(e) { - Zotero.debug("Translate: Error adding attachment "+attachment.url, 2); - } - } - } - } - } else if(this.type == "import") { - // create new attachments attached here - attachment.itemType = 'attachment'; - this._itemDone(attachment, myID); - } - } - } - } - - if(item.itemID) { - this._IDMap[item.itemID] = myID; - } - if(!attachedTo) { - this.newItems.push(myID); - } - - // handle see also - if(item.seeAlso) { - for each(var seeAlso in item.seeAlso) { - if(this._IDMap[seeAlso]) { - newItem.addRelatedItem(this._IDMap[seeAlso]); - } - } - newItem.save(); - } - - // handle tags, if this is an import translation or automatic tagging is - // enabled in the preferences (as it is by default) - if(item.tags && - (this.type == "import" || Zotero.Prefs.get("automaticTags"))) { - var tagsToAdd = {}; - tagsToAdd[0] = []; // user tags - tagsToAdd[1] = []; // automatic tags - - for each(var tag in item.tags) { - if(typeof(tag) == "string") { - // accept strings in tag array as automatic tags, or, if - // importing, as non-automatic tags - if (this.type == 'import') { - tagsToAdd[0].push(tag); - } - else { - tagsToAdd[1].push(tag); - } - } else if(typeof(tag) == "object") { - // also accept objects - if(tag.tag) { - if (this.type == "import") { - if (!tagsToAdd[tag.type]) { - tagsToAdd[tag.type] = []; - } - tagsToAdd[tag.type].push(tag.tag); - } - // force web imports to automatic - else { - tagsToAdd[1].push(tag.tag); - } - } - } - } - - for (var type in tagsToAdd) { - if (tagsToAdd[type].length) { - newItem.addTags(tagsToAdd[type], type); - } - } - } - - if(!attachedTo) { - // Re-retrieve item before passing to handler - newItem = Zotero.Items.get(newItem.id); - this.runHandler("itemDone", newItem); - } - - delete item; - - // Allow progress meter to update - // - // This can probably be re-enabled for web translators once badly asynced ones are fixed - if (this.type == 'import' || this.type == 'export') { - Zotero.wait(); - } -} - -/* - * executed when a collection is done and ready to be loaded into the database - */ -Zotero.Translate.prototype._collectionDone = function(collection) { - var newCollection = this._processCollection(collection, null); - - this.runHandler("collectionDone", newCollection); -} - -/* - * recursively processes collections - */ -Zotero.Translate.prototype._processCollection = function(collection, parentID) { - var newCollection = Zotero.Collections.add(collection.name, parentID); - var myID = newCollection.id; - - this.newCollections.push(myID); - - var toAdd = []; - - for each(child in collection.children) { - if(child.type == "collection") { - // do recursive processing of collections - this._processCollection(child, myID); - } else { - // add mapped items to collection - if(this._IDMap[child.id]) { - toAdd.push(this._IDMap[child.id]); - } else { - Zotero.debug("Translate: Could not map "+child.id+" to an imported item", 2); - } - } - } - - if (toAdd.length) { - Zotero.debug("Translate: Adding " + toAdd, 5); - newCollection.addItems(toAdd); - } - - return newCollection; -} - -/* - * logs a debugging message - */ -Zotero.Translate.prototype._debug = function(string, level) { - if(typeof string === "object") string = new XPCSafeJSObjectWrapper(string); - if(level !== undefined && typeof level !== "number") { - Zotero.debug("debug: level must be an integer"); - return; - } - - // if handler does not return anything explicitly false, show debug - // message in console - if(this.runHandler("debug", string) !== false) { - if(typeof string == "string") string = "Translate: "+string; - Zotero.debug(string, level); - } -} - -/* - * does the actual web translation - */ -Zotero.Translate.prototype._web = function() { - try { - this._sandbox.doWeb(this.document, this.location); - } catch(e) { - if(typeof e === "object" && e !== null) { - e = new XPCSafeJSObjectWrapper(e); - } - - if(this._parentTranslator) { - throw(e); - } else { - this._translationComplete(false, e); - return false; - } - } - - return true; -} - -/** - * Does the actual search translation - **/ -Zotero.Translate.prototype._search = function() { - try { - this._sandbox.doSearch(this.search); - } catch(e) { - if(typeof e === "object" && e !== null) { - e = new XPCSafeJSObjectWrapper(e); - } - - this._translationComplete(false, e); - return false; - } - - return true; -} - -/** - * Does the actual import translation - **/ -Zotero.Translate.prototype._import = function() { - this.waitForCompletion = true; - this._importSniffCharacterSet(); -} - -/** - * Sniff file for its character set, then proceed with the rest of translation - */ -Zotero.Translate.prototype._importSniffCharacterSet = function(callback) { - if(!this._storage) { - if(this._charset) { - // have charset already; just go on - this._importDoneSniffing(this._charset); - } else { - // need to check charset - importCharset = Zotero.Prefs.get("import.charset"); - if(importCharset == "auto") { - // look for charset - var me = this; - Zotero.File.getCharsetFromFile(this.location, "text/plain", - function(charset) { - // Default to UTF-8 if no charset available - me._charset = charset ? charset : "UTF-8"; - me._importDoneSniffing(charset); - }); - } else { - this._importDoneSniffing(importCharset); - } - } - } else { - this._importDoneSniffing(); - } -} - -/** - * Complete import (used as callback after sniffing) - **/ -Zotero.Translate.prototype._importDoneSniffing = function(charset) { - this._importConfigureIO(charset); - - try { - this._sandbox.doImport(); - } catch(e) { - if(typeof e === "object" && e !== null) { - e = new XPCSafeJSObjectWrapper(e); - } - - if(this._parentTranslator) { - throw(e); - } else { - this._translationComplete(false, e); - return false; - } - } - this._translationComplete(true); -} - -/* - * set up import for IO - */ -Zotero.Translate.prototype._importConfigureIO = function(charset) { - if(this._storage) { - // import from string - this._storagePointer = 0; - - if(this.configOptions.dataMode && this.configOptions.dataMode == "xml/dom" || this.configOptions.dataMode == "rdf") { - // for DOM XML, handle with parseFromString - if(this.configOptions.dataMode == "xml/dom" || !this._rdf) { - var xmlNodes = Components.classes["@mozilla.org/xmlextras/domparser;1"] - .createInstance(Components.interfaces.nsIDOMParser) - .parseFromString(this._storage, "text/xml"); - } - } else if(this.configOptions.dataMode && this.configOptions.dataMode == "xml/e4x") { - var xmlNodes = new XML(this._storage.replace(/<\?xml[^>]+\?>/, "")); - } else { - this._storageFunctions(true); - } - } else { - // import from file - - var me = this; - // open file and set read methods - if(this._inputStream) { - this._inputStream.QueryInterface(Components.interfaces.nsISeekableStream) - .seek(Components.interfaces.nsISeekableStream.NS_SEEK_SET, 0); - this._inputStream.QueryInterface(Components.interfaces.nsIFileInputStream); - } else { - this._inputStream = Components.classes["@mozilla.org/network/file-input-stream;1"] - .createInstance(Components.interfaces.nsIFileInputStream); - this._inputStream.init(this.location, 0x01, 0664, 0); - this._streams.push(this._inputStream); - } - - var sStream = null; - var bomLength = 0; - if(!charset && this._charset) charset = this._charset; - if(!charset || (charset && charset.length > 3 && charset.substr(0, 3).toUpperCase() == "UTF")) { - // seek past BOM - var bomCharset = this._importGetBOM(); - var bomLength = (bomCharset ? BOMs[bomCharset].length : 0); - this._inputStream.QueryInterface(Components.interfaces.nsISeekableStream) - .seek(Components.interfaces.nsISeekableStream.NS_SEEK_SET, bomLength); - if(bomCharset) charset = this._charset = bomCharset; - } - - // look for/seek past XML charset declaration - if(this.configOptions.dataMode && (this.configOptions.dataMode == "xml/e4x" - || this.configOptions.dataMode == "xml/dom" - || this.configOptions.dataMode == "rdf")) { - - sStream = Components.classes["@mozilla.org/scriptableinputstream;1"] - .createInstance(Components.interfaces.nsIScriptableInputStream); - sStream.init(this._inputStream); - this._streams.push(sStream); - - // read until we see if the file begins with a parse instruction - const whitespaceRe = /\s/g; - var read; - do { - read = sStream.read(1); - } while(whitespaceRe.test(read)) - - if(read != "<") throw "XML load error: text does not start with <"; - - var firstPart = read + sStream.read(4); - if(firstPart == "")) { - read = sStream.read(1); - firstPart += read; - } - - var encodingRe = /encoding=['"]([^'"]+)['"]/; - var m = encodingRe.exec(firstPart); - if(m) { - try { - var charconv = Components.classes["@mozilla.org/charset-converter-manager;1"] - .getService(Components.interfaces.nsICharsetConverterManager) - .getCharsetTitle(m[1]); - if(charconv) charset = this._charset = m[1]; - } catch(e) {} - } - } else { - this._inputStream.QueryInterface(Components.interfaces.nsISeekableStream) - .seek(Components.interfaces.nsISeekableStream.NS_SEEK_SET, bomLength); - } - - if(!charset) charset = "UTF-8"; - } - - if(this.configOptions.dataMode && this.configOptions.dataMode == "xml/dom" || this.configOptions.dataMode == "rdf") { - // for DOM XML, pass charset to parseFromStream - if(this.configOptions.dataMode == "xml/dom" || !this._rdf) { - var xmlNodes = Components.classes["@mozilla.org/xmlextras/domparser;1"] - .createInstance(Components.interfaces.nsIDOMParser) - .parseFromStream(this._inputStream, charset, this.location.fileSize, "text/xml"); - } - } else { - var intlStream = null; - if(charset) { - // if have detected charset - Zotero.debug("Translate: Using detected character set "+charset, 3); - // convert from detected charset - intlStream = Components.classes["@mozilla.org/intl/converter-input-stream;1"] - .createInstance(Components.interfaces.nsIConverterInputStream); - intlStream.init(this._inputStream, charset, 65535, - Components.interfaces.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER); - me._streams.push(intlStream); - } - - if(this.configOptions.dataMode && this.configOptions.dataMode == "xml/e4x") { - // read in 16384 byte increments - var xmlNodes = ""; - var str = {}; - while(intlStream.readString(16384, str)) { - xmlNodes += str.value; - } - - // create xml - xmlNodes = new XML(xmlNodes); - } else { - // standard text reading tools - - // allow translator to set charset - this._sandbox.Zotero.setCharacterSet = function(charset) { - if(typeof charset !== "string") { - throw "setCharacterSet: charset must be a string"; - } - - // seek back to the beginning - me._inputStream.QueryInterface(Components.interfaces.nsISeekableStream) - .seek(Components.interfaces.nsISeekableStream.NS_SEEK_SET, bomLength); - - intlStream = Components.classes["@mozilla.org/intl/converter-input-stream;1"] - .createInstance(Components.interfaces.nsIConverterInputStream); - try { - intlStream.init(me._inputStream, charset, 65535, - Components.interfaces.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER); - } catch(e) { - throw "setCharacterSet: text encoding not supported"; - } - me._streams.push(intlStream); - } - - var str = new Object(); - if(this.configOptions.dataMode && this.configOptions.dataMode == "line") { // line by line reading - this._inputStream.QueryInterface(Components.interfaces.nsILineInputStream); - - this._sandbox.Zotero.read = function() { - if(intlStream && intlStream instanceof Components.interfaces.nsIUnicharLineInputStream) { - var amountRead = intlStream.readLine(str); - } else { - var amountRead = me._inputStream.readLine(str); - } - if(amountRead) { - return str.value; - } else { - return false; - } - } - } else { // block reading - this._sandbox.Zotero.read = function(amount) { - if(typeof amount === "object") { - throw "read: amount must be an integer"; - } - - if(intlStream) { - // read from international stream, if one is available - var amountRead = intlStream.readString(amount, str); - - if(amountRead) { - return str.value; - } else { - return false; - } - } else { - // allocate sStream on the fly - if(!sStream) { - sStream = Components.classes["@mozilla.org/scriptableinputstream;1"] - .createInstance(Components.interfaces.nsIScriptableInputStream); - sStream.init(me._inputStream); - me._streams.push(sStream); - } - - // read from the scriptable input stream - var string = sStream.read(amount); - return string; - } - } - } - } - } - } - - if(this.configOptions.dataMode) { - // make sure DOM XML actually got parsed - if(xmlNodes && (this.configOptions.dataMode == "rdf" || this.configOptions.dataMode == "xml/dom") - && xmlNodes.getElementsByTagName("parsererror").length) { - this._rdf = undefined; - throw("XML parser error: loading data into data store failed"); - } - - if(this.configOptions.dataMode == "rdf") { - // set up RDF - if(!this._rdf) { - // get URI - var IOService = Components.classes['@mozilla.org/network/io-service;1'] - .getService(Components.interfaces.nsIIOService); - if(this._storage) { - var baseURI = this.location ? this.location : ""; - } else { - var fileHandler = IOService.getProtocolHandler("file") - .QueryInterface(Components.interfaces.nsIFileProtocolHandler); - var baseURI = fileHandler.getURLSpecFromFile(this.location); - } - - Zotero.debug("Translate: initializing data store"); - this._rdf = new Zotero.RDF.AJAW.RDFIndexedFormula(); - - Zotero.debug("Translate: loading data"); - var parser = new Zotero.RDF.AJAW.RDFParser(this._rdf); - parser.parse(xmlNodes, baseURI); - } - - // add RDF features to sandbox - this._sandbox.Zotero.RDF = new Zotero.Translate.RDF(this._rdf); - } else if(this.configOptions.dataMode == "xml/e4x" || this.configOptions.dataMode == "xml/dom") { - // add getXML function - this._sandbox.Zotero.getXML = function() { - return xmlNodes; - } - } - } -} - -/** - * Searches for a UTF BOM at the beginning of the input stream. - * - * @return The length of the UTF BOM. - */ -Zotero.Translate.prototype._importGetBOM = function() { - // if not checked for a BOM, open a binary input stream and read - var binStream = Components.classes["@mozilla.org/binaryinputstream;1"]. - createInstance(Components.interfaces.nsIBinaryInputStream); - binStream.setInputStream(this._inputStream); - - var possibleBOMs = BOMs; - var couldHaveBOM = true; - var newBOMs, readByte; - - while(couldHaveBOM) { - newBOMs = {}; - couldHaveBOM = false; - - readByte = binStream.read8(); - readChar = String.fromCharCode(readByte) - - for(var charset in possibleBOMs) { - if(possibleBOMs[charset][0] == readChar) { - if(possibleBOMs[charset].length == 1) { - // have checked entire BOM - return charset; - } else { - // keep checking - newBOMs[charset] = possibleBOMs[charset].substr(1); - couldHaveBOM = true; - } - } - } - - possibleBOMs = newBOMs; - } - - return null; -} - -/** - * Does the actual export, after code has been loaded and parsed - */ -Zotero.Translate.prototype._export = function() { - - if(this.items) { - // if just items, get them and don't worry about collection logic - this._itemsLeft = this.items; - } else if(this.collection) { - // get items in this collection - this._itemsLeft = this.collection.getChildItems(); - if(!this._itemsLeft) { - this._itemsLeft = []; - } - - if(this.configOptions.getCollections) { - // get child collections - this._collectionsLeft = Zotero.getCollections(this.collection.id, true); - - if(this._collectionsLeft.length) { - // only include parent collection if there are actually children - this._collectionsLeft.unshift(this.collection); - } - - // get items in child collections - for each(var collection in this._collectionsLeft) { - var childItems = collection.getChildItems(); - if(childItems) { - this._itemsLeft = this._itemsLeft.concat(childItems); - } - } - } - } else { - // get all top-level items - this._itemsLeft = Zotero.Items.getAll(true); - - if(this.configOptions.getCollections) { - // get all collections - this._collectionsLeft = Zotero.getCollections(); - } - } - - // export file data, if requested - if(this.displayOptions["exportFileData"]) { - // generate directory - var directory = Components.classes["@mozilla.org/file/local;1"]. - createInstance(Components.interfaces.nsILocalFile); - directory.initWithFile(this.location.parent); - - // delete this file if it exists - if(this.location.exists()) { - this.location.remove(true); - } - - // get name - var name = this.location.leafName; - directory.append(name); - - // create directory - directory.create(Components.interfaces.nsIFile.DIRECTORY_TYPE, 0700); - - // generate a new location for the exported file, with the appropriate - // extension - this.location = Components.classes["@mozilla.org/file/local;1"]. - createInstance(Components.interfaces.nsILocalFile); - this.location.initWithFile(directory); - this.location.append(name+"."+this.translator[0].target); - - // create files directory - this._exportFileDirectory = Components.classes["@mozilla.org/file/local;1"]. - createInstance(Components.interfaces.nsILocalFile); - this._exportFileDirectory.initWithFile(directory); - this._exportFileDirectory.append("files"); - this._exportFileDirectory.create(Components.interfaces.nsIFile.DIRECTORY_TYPE, 0700); - } - - // configure IO - this._exportConfigureIO(); - - try { - this._sandbox.doExport(); - } catch(e) { - if(typeof e === "object" && e !== null) { - e = new XPCSafeJSObjectWrapper(e); - } - - this._translationComplete(false, e); - return false; - } - - return true; -} - -/** - * Configures the output stream for export and adds writing functions to the - * sandbox - */ -Zotero.Translate.prototype._exportConfigureIO = function() { - if(this.location) { - // open file - var fStream = Components.classes["@mozilla.org/network/file-output-stream;1"] - .createInstance(Components.interfaces.nsIFileOutputStream); - fStream.init(this.location, 0x02 | 0x08 | 0x20, 0664, 0); // write, create, truncate - // attach to stack of streams to close at the end - this._streams.push(fStream); - } - - if(this.configOptions.dataMode && (this.configOptions.dataMode == "rdf" || this.configOptions.dataMode == "rdf/n3")) { // rdf io - // initialize data store - this._rdf = new Zotero.RDF.AJAW.RDFIndexedFormula(); - - // add RDF features to sandbox - this._sandbox.Zotero.RDF = new Zotero.Translate.RDF(this._rdf); - } else { - // regular io; write just writes to file or string - if(this.location) { - var intlStream = null; - var writtenToStream = false; - var streamCharset = null; - - // allow setting of character sets - this._sandbox.Zotero.setCharacterSet = function(charset) { - if(typeof charset !== "string") { - throw "setCharacterSet: charset must be a string"; - } - - streamCharset = charset.toUpperCase(); - intlStream = Components.classes["@mozilla.org/intl/converter-output-stream;1"] - .createInstance(Components.interfaces.nsIConverterOutputStream); - if(charset == "UTF-8xBOM") charset = "UTF-8"; - intlStream.init(fStream, charset, 1024, "?".charCodeAt(0)); - }; - - // if exportCharset option was presented to user, use the result - if(this.displayOptions.exportCharset) { - this._sandbox.Zotero.setCharacterSet(this.displayOptions.exportCharset); - } - - this._sandbox.Zotero.write = function(data) { - if(typeof data === "object") data = new XPCSafeJSObjectWrapper(data); - - if(streamCharset) { - if(!writtenToStream && BOMs[streamCharset]) { - // If stream has not yet been written to, and a UTF type - // has been selected, write the BOM - fStream.write(BOMs[streamCharset], BOMs[streamCharset].length); - } - - if(streamCharset == "MACINTOSH") { - // fix buggy Mozilla MacRoman - splitData = data.split(/([\r\n]+)/); - for(var i=0; i= me._storageLength) { - return false; - } - - var oldPointer = me._storagePointer; - var lfIndex = me._storage.indexOf("\n", me._storagePointer); - - if(lfIndex != -1) { - // in case we have a CRLF - me._storagePointer = lfIndex+1; - if(me._storageLength > lfIndex && me._storage[lfIndex-1] == "\r") { - lfIndex--; - } - return me._storage.substr(oldPointer, lfIndex-oldPointer); - } - - var crIndex = me._storage.indexOf("\r", me._storagePointer); - if(crIndex != -1) { - me._storagePointer = crIndex+1; - return me._storage.substr(oldPointer, crIndex-oldPointer-1); - } - - me._storagePointer = me._storageLength; - return me._storage.substr(oldPointer); - } - } else { // block reading - this._sandbox.Zotero.read = function(amount) { - if(me._storagePointer >= me._storageLength) { - return false; - } - - if((me._storagePointer+amount) > me._storageLength) { - var oldPointer = me._storagePointer; - me._storagePointer = me._storageLength+1; - return me._storage.substr(oldPointer); - } - - var oldPointer = me._storagePointer; - me._storagePointer += amount; - return me._storage.substr(oldPointer, amount); - } - } - } -} - -/* Zotero.Translate.ZoteroItem: a class to perform recursive translator searches - * by waiting for completion of each translator - */ - -Zotero.Translate.TranslatorSearch = function(translate, translators) { - // generate a copy of the translator search array - this.translate = translate; - this.allTranslators = translators; - this.translators = this.allTranslators.slice(0); - this.foundTranslators = new Array(); - this.ignoreExtensions = false; - this.asyncMode = false; - - this.running = true; - this.execute(); -} - -/* - * Check to see if a list of translators can scrape the page passed to the - * translate() instance - */ -Zotero.Translate.TranslatorSearch.prototype.execute = function() { - if(!this.running) return; - if(this.checkDone()) return; - - // get next translator - var translator = this.translators.shift(); - - if((this.translate.type == "import" || this.translate.type == "web") && !this.translate.location && !this.translate._storage) { - // if no location yet (e.g., getting list of possible web translators), - // just return true - this.addTranslator(translator); - this.execute(); - return; - } - - // Test location with regular expression - var checkDetectCode = true; - if(translator.target && (this.translate.type == "import" || this.translate.type == "web")) { - var checkDetectCode = false; - - if(this.translate.type == "web") { - var regularExpression = translator.webRegexp; - } else { - var regularExpression = translator.importRegexp; - } - - if(regularExpression.test(this.translate.path)) { - checkDetectCode = true; - } - - if(this.ignoreExtensions) { - // if we're ignoring extensions, that means we already tried - // everything without ignoring extensions and it didn't work - checkDetectCode = !checkDetectCode; - } - } - - // Test with JavaScript if available and didn't have a regular expression or - // passed regular expression test - if(checkDetectCode) { - // parse the detect code and execute - this.translate._parseCode(translator); - - if(this.translate.type == "import") { - var me = this; - - try { - this.translate._importConfigureIO(); // so it can read - } catch(e) { - Zotero.debug("Translate: "+e+' in opening IO for '+translator.label); - this.execute(); - return; - } - } - this.runDetectCode(translator); - } else { - this.execute(); - } -} - -/** - * Sets options and runs detectCode - **/ -Zotero.Translate.TranslatorSearch.prototype.runDetectCode = function(translator) { - translator.configOptions = this.translate.configOptions; - translator.displayOptions = this.translate.displayOptions; - - if((this.translate.type == "web" && this.translate._sandbox.detectWeb) || - (this.translate.type == "search" && this.translate._sandbox.detectSearch) || - (this.translate.type == "import" && this.translate._sandbox.detectImport)) { - var returnValue; - - this.currentTranslator = translator; - try { - if(this.translate.type == "web") { - returnValue = this.translate._sandbox.detectWeb(this.translate.document, this.translate.location); - } else if(this.translate.type == "search") { - returnValue = this.translate._sandbox.detectSearch(this.translate.search); - } else if(this.translate.type == "import") { - returnValue = this.translate._sandbox.detectImport(); - } - } catch(e) { - if(typeof e === "object" && e !== null) { - e = new XPCSafeJSObjectWrapper(e); - } - - this.complete(returnValue, e); - return; - } - - Zotero.debug("Translate: Executed detectCode for "+translator.label, 4); - - if(this.translate.type == "web" && this.translate.waitForCompletion) { - this.asyncMode = true; - - // don't immediately execute next - return; - } else if(returnValue) { - this.processReturnValue(translator, returnValue); - } - } else { - // add translator even though it has no proper detectCode (usually - // export translators, which have options but do nothing with them) - this.addTranslator(translator); - } - - this.execute(); -} - -/* - * Determines whether all translators have been processed - */ -Zotero.Translate.TranslatorSearch.prototype.checkDone = function() { - if(this.translators.length == 0) { - // if we've gone through all of the translators, trigger the handler - if(this.foundTranslators.length) { - this.translate.runHandler("translators", this.foundTranslators); - return true; - } else if(this.translate.type == "import" && !this.ignoreExtensions) { - // if we fail the first time finding an import translator, search - // again, but ignore extensions - this.ignoreExtensions = true; - this.translators = this.allTranslators.slice(0); - } else { - this.translate.runHandler("translators", false); - return true; - } - } - - return false; -} - -/* - * Processes the return value from a translator - */ -Zotero.Translate.TranslatorSearch.prototype.processReturnValue = function(translator, returnValue) { - Zotero.debug("Translate: Found translator "+translator.label, 3); - - if(typeof(returnValue) == "string") { - translator.itemType = returnValue; - } - this.addTranslator(translator); -} - -/* - * Called upon completion of asynchronous translator search - */ -Zotero.Translate.TranslatorSearch.prototype.complete = function(returnValue, error) { - // reset done function - this.translate._sandbox.Zotero.done = undefined; - this.translate.waitForCompletion = false; - this.asyncMode = false; - - if(returnValue) { - this.processReturnValue(this.currentTranslator, returnValue); - } else if(error) { - var errorString = this.translate._generateErrorString(error); - this.currentTranslator.logError(error, "warning"); - this.translate._debug("detectCode for "+(this.currentTranslator ? this.currentTranslator.label : "no translator")+" failed: \n"+errorString, 4); - } - - this.currentTranslator = undefined; - - // resume execution - this.execute(); -} - -/* - * Copies a translator to the foundTranslators list - */ -Zotero.Translate.TranslatorSearch.prototype.addTranslator = function(translator) { - var newTranslator = new Object(); - for(var i in translator) { - newTranslator[i] = translator[i]; - } - this.foundTranslators.push(newTranslator); -} - -/* Zotero.Translate.ZoteroItem: a class for generating a new item from - * inside scraper code - */ - -Zotero.Translate.GenerateZoteroItemClass = function() { - var ZoteroItem = function(itemType) { - // assign item type - this.itemType = itemType; - // generate creators array - this.creators = new Array(); - // generate notes array - this.notes = new Array(); - // generate tags array - this.tags = new Array(); - // generate see also array - this.seeAlso = new Array(); - // generate file array - this.attachments = new Array(); - }; - - return ZoteroItem; -} - -/* Zotero.Translate.Collection: a class for generating a new top-level - * collection from inside scraper code - */ - -Zotero.Translate.GenerateZoteroCollectionClass = function() { - var ZoteroCollection = function() {}; - - return ZoteroCollection; -} - -/* Zotero.Translate.RDF: a class for handling RDF IO - * - * If an import/export translator specifies dataMode RDF, this is the interface, - * accessible from model. - */ - -Zotero.Translate.RDF = function(dataStore) { - this._dataStore = dataStore; - this._containerCounts = {}; -} - -// get a resource as an nsIRDFResource, instead of a string -Zotero.Translate.RDF.prototype._getResource = function(about) { - return (typeof about == "object" ? about : new Zotero.RDF.AJAW.RDFSymbol(about)); -} - - -// USED FOR OUTPUT - -// writes an RDF triple -Zotero.Translate.RDF.prototype.addStatement = function(about, relation, value, literal) { - if(literal) { - // zap chars that Mozilla will mangle - value = value.toString().replace(/[\x00-\x08\x0B\x0C\x0E-\x1F]/g, ''); - } else { - value = this._getResource(value); - } - - this._dataStore.add(this._getResource(about), this._getResource(relation), value); -} - -// creates an anonymous resource -Zotero.Translate.RDF.prototype.newResource = function() { - return new Zotero.RDF.AJAW.RDFBlankNode(); -}; - -// creates a new container -Zotero.Translate.RDF.prototype.newContainer = function(type, about) { - const rdf = "http://www.w3.org/1999/02/22-rdf-syntax-ns#"; - const containerTypes = {"bag":"Bag", "seq":"Seq", "alt":"Alt"}; - - type = type.toLowerCase(); - if(!containerTypes[type]) { - throw "Invalid container type in Zotero.RDF.newContainer"; - } - - var about = this._getResource(about); - this.addStatement(about, rdf+"type", rdf+containerTypes[type], false); - this._containerCounts[about.toNT()] = 1; - - return about; -} - -// adds a new container element -Zotero.Translate.RDF.prototype.addContainerElement = function(about, element, literal) { - const rdf = "http://www.w3.org/1999/02/22-rdf-syntax-ns#"; - - var about = this._getResource(about); - this._dataStore.add(about, new Zotero.RDF.AJAW.RDFSymbol(rdf+"_"+(this._containerCounts[about.toNT()]++)), element, literal); -} - -// gets container elements as an array -Zotero.Translate.RDF.prototype.getContainerElements = function(about) { - const liPrefix = "http://www.w3.org/1999/02/22-rdf-syntax-ns#_"; - - var about = this._getResource(about); - var statements = this._dataStore.statementsMatching(about); - var containerElements = []; - - // loop over arcs out looking for list items - for each(var statement in statements) { - if(statement.predicate.uri.substr(0, liPrefix.length) == liPrefix) { - var number = statement.predicate.uri.substr(liPrefix.length); - - // make sure these are actually numeric list items - var intNumber = parseInt(number); - if(number == intNumber.toString()) { - // add to element array - containerElements[intNumber-1] = (statement.object.termType == "literal" ? statement.object.toString() : statement.object); - } - } - } - - return containerElements; -} - -// sets a namespace -Zotero.Translate.RDF.prototype.addNamespace = function(prefix, uri) { - this._dataStore.setPrefixForURI(prefix, uri); -} - -// gets a resource's URI -Zotero.Translate.RDF.prototype.getResourceURI = function(resource) { - if(typeof(resource) == "string") return resource; - if(resource.uri) return resource.uri; - if(resource.toNT == undefined) throw "Zotero.RDF: getResourceURI called on invalid resource"; - return resource.toNT(); -} - -// USED FOR INPUT - -// gets all RDF resources -Zotero.Translate.RDF.prototype.getAllResources = function() { - return [s[0].subject for each(s in this._dataStore.subjectIndex)]; -} - -// gets arcs going in -Zotero.Translate.RDF.prototype.getArcsIn = function(resource) { - var statements = this._dataStore.objectIndex[this._dataStore.canon(this._getResource(resource))]; - if(!statements) return false; - return [s.predicate for each(s in statements)]; -} - -// gets arcs going out -Zotero.Translate.RDF.prototype.getArcsOut = function(resource) { - var statements = this._dataStore.subjectIndex[this._dataStore.canon(this._getResource(resource))]; - if(!statements) return false; - return [s.predicate.uri for each(s in statements)]; -} - -// gets source resources -Zotero.Translate.RDF.prototype.getSources = function(resource, property) { - var statements = this._dataStore.statementsMatching(undefined, this._getResource(property), this._getResource(resource)); - if(!statements.length) return false; - return [s.subject for each(s in statements)]; -} - -// gets target resources -Zotero.Translate.RDF.prototype.getTargets = function(resource, property) { - var statements = this._dataStore.statementsMatching(this._getResource(resource), this._getResource(property)); - if(!statements.length) return false; - return [(s.object.termType == "literal" ? s.object.toString() : s.object) for each(s in statements)]; -} - -/** - * Gets statements matching a certain pattern - * - * @param {String|Zotero.RDF.AJAW.RDFSymbol} subj Subject - * @param {String|Zotero.RDF.AJAW.RDFSymbol} predicate Predicate - * @param {String|Zotero.RDF.AJAW.RDFSymbol} obj Object - * @param {Boolean} objLiteral Whether the object is a literal (as - * opposed to a URI) - * @param {Boolean} justOne Whether to stop when a single result is - * retrieved - */ -Zotero.Translate.RDF.prototype.getStatementsMatching = function(subj, pred, obj, objLiteral, justOne) { - var statements = this._dataStore.statementsMatching( - (subj ? this._getResource(subj) : undefined), - (pred ? this._getResource(pred) : undefined), - (obj ? (objLiteral ? objLiteral : this._getResource(obj)) : undefined), - undefined, justOne); - if(!statements.length) return false; - return [[s.subject, s.predicate, (s.object.termType == "literal" ? s.object.toString() : s.object)] for each(s in statements)]; -} - -/* - * Wrap arguments to RDF functions in XPCSafeJSObjectWrappers - */ -for(var wrapFunctionBad in Zotero.Translate.RDF) { - let wrapFunction = wrapFunctionBad; - if(!(Zotero.Translate.RDF[wrapFunction] instanceof Function)) continue; - - let unwrappedFunction = Zotero.Translate.RDF[wrapFunction]; - - Zotero.Translate.RDF.prototype[wrapFunction] = function() { - let args = []; - for(let i=0; i. + + ***** END LICENSE BLOCK ***** +*/ + +const BOMs = { + "UTF-8":"\xEF\xBB\xBF", + "UTF-16BE":"\xFE\xFF", + "UTF-16LE":"\xFF\xFE", + "UTF-32BE":"\x00\x00\xFE\xFF", + "UTF-32LE":"\xFF\xFE\x00\x00" +} + +Components.utils.import("resource://gre/modules/NetUtil.jsm"); + +/** + * @class Manages the translator sandbox + * @param {Zotero.Translate} translate + * @param {String|window} sandboxLocation + */ +Zotero.Translate.SandboxManager = function(translate, sandboxLocation) { + this.sandbox = new Components.utils.Sandbox(sandboxLocation); + this.sandbox.Zotero = {}; + this._translate = translate; + + // import functions missing from global scope into Fx sandbox + this.sandbox.XPathResult = Components.interfaces.nsIDOMXPathResult; +} + +Zotero.Translate.SandboxManager.prototype = { + /** + * Evaluates code in the sandbox + */ + "eval":function(code) { + Components.utils.evalInSandbox(code, this.sandbox); + }, + + /** + * Imports an object into the sandbox + * + * @param {Object} object Object to be imported (under Zotero) + * @param {Boolean} passTranslateAsFirstArgument Whether the translate instance should be passed + * as the first argument to the function. + */ + "importObject":function(object, passAsFirstArgument, attachTo) { + if(!attachTo) attachTo = this.sandbox.Zotero; + for(var key in (object.__exposedProps__ ? object.__exposedProps__ : object)) { + let localKey; + if(object.__exposedProps__) { + localKey = object.__exposedProps__[key]; + } else { + localKey = key; + } + + // magical XPCSafeJSObjectWrappers for sandbox + if(typeof object[localKey] === "function" || typeof object[localKey] === "object") { + attachTo[localKey] = function() { + var args = (passAsFirstArgument ? [passAsFirstArgument] : []); + for(var i=0; i")) { + read = sStream.read(1); + firstPart += read; + } + + const encodingRe = /encoding=['"]([^'"]+)['"]/; + var m = encodingRe.exec(firstPart); + if(m) { + try { + var charconv = Components.classes["@mozilla.org/charset-converter-manager;1"] + .getService(Components.interfaces.nsICharsetConverterManager) + .getCharsetTitle(m[1]); + if(charconv) this._charset = m[1]; + } catch(e) {} + } + + // if we know for certain document is XML, we also know for certain that the + // default charset for XML is UTF-8 + if(!this._charset) this._charset = "UTF-8"; + } + } + + // If we managed to get a charset here, then translators shouldn't be able to override it, + // since it's almost certainly correct. Otherwise, we allow override. + this._allowCharsetOverride = !!this._charset; + this._seekToStart(); + + if(!this._charset) { + // No XML parse instruction or BOM. + + // Check whether the user has specified a charset preference + var charsetPref = Zotero.Prefs.get("import.charset"); + if(charsetPref == "auto") { + // For auto-detect, we are basically going to check if the file could be valid + // UTF-8, and if this is true, we will treat it as UTF-8. Prior likelihood of + // UTF-8 is very high, so this should be a reasonable strategy. + + // from http://codex.wordpress.org/User:Hakre/UTF8 + const UTF8Regex = new RegExp('^(?:' + + '[\x09\x0A\x0D\x20-\x7E]' + // ASCII + '|[\xC2-\xDF][\x80-\xBF]' + // non-overlong 2-byte + '|\xE0[\xA0-\xBF][\x80-\xBF]' + // excluding overlongs + '|[\xE1-\xEC\xEE][\x80-\xBF]{2}' + // 3-byte, but exclude U-FFFE and U-FFFF + '|\xEF[\x80-\xBE][\x80-\xBF]' + + '|\xEF\xBF[\x80-\xBD]' + + '|\xED[\x80-\x9F][\x80-\xBF]' + // excluding surrogates + '|\xF0[\x90-\xBF][\x80-\xBF]{2}' + // planes 1-3 + '|[\xF1-\xF3][\x80-\xBF]{3}' + // planes 4-15 + '|\xF4[\x80-\x8F][\x80-\xBF]{2}' + // plane 16 + ')*$'); + + // Read all currently available bytes from file. I'm not sure how many this is + // but it's a safe bet that we don't want to try to read any more than this, since + // it would slow things down considerably. + if(Zotero.isFx4) { + var fileContents = NetUtil.readInputStreamToString(this._rawStream, this._rawStream.available()); + } else { + var fileContents = binStream.readByteArray(this._rawStream.available()); + fileContents = String.fromCharCode.apply(null, fileContents); + } + + // Seek back to beginning of file + this._seekToStart(); + + // See whether this could be UTF-8 + if(UTF8Regex.test(fileContents)) { + // Assume this is UTF-8 + this._charset = "UTF-8"; + } else { + // Can't be UTF-8; see if a default charset is defined + this._charset = Zotero.Prefs.get("intl.charset.default", true); + + // ISO-8859-1 by default + if(!this._charset) this._charset = "ISO-8859-1"; + } + } else { + // No need to auto-detect; user has specified a charset + this._charset = charsetPref; + } + } + } + + Zotero.debug("Translate: Detected file charset as "+this._charset); + + // We know the charset now. Open a converter stream. + if(mode) this.reset(mode); +} + +Zotero.Translate.IO.Read.prototype = { + "__exposedProps__":["getXML", "RDF", "read", "setCharacterSet"], + + "_seekToStart":function() { + this._rawStream.QueryInterface(Components.interfaces.nsISeekableStream) + .seek(Components.interfaces.nsISeekableStream.NS_SEEK_SET, this._bomLength); + }, + + "_readToString":function() { + var str = {}; + this.inputStream.readString(this.file.fileSize, str); + return str.value; + }, + + "_initRDF":function() { + // get URI + var IOService = Components.classes['@mozilla.org/network/io-service;1'] + .getService(Components.interfaces.nsIIOService); + var fileHandler = IOService.getProtocolHandler("file") + .QueryInterface(Components.interfaces.nsIFileProtocolHandler); + var baseURI = fileHandler.getURLSpecFromFile(this.file); + + Zotero.debug("Translate: Initializing RDF data store"); + this._dataStore = new Zotero.RDF.AJAW.RDFIndexedFormula(); + var parser = new Zotero.RDF.AJAW.RDFParser(this._dataStore); + var nodes = Zotero.Translate.IO.parseDOMXML(this._rawStream, this._charset, this.file.fileSize); + parser.parse(nodes, baseURI); + + this.RDF = new Zotero.Translate.IO._RDFSandbox(this._dataStore); + }, + + "setCharacterSet":function(charset) { + if(typeof charset !== "string") { + throw "Translate: setCharacterSet: charset must be a string"; + } + + // seek back to the beginning + this._seekToStart(); + + if(this._allowCharsetOverride) { + this.inputStream = Components.classes["@mozilla.org/intl/converter-input-stream;1"] + .createInstance(Components.interfaces.nsIConverterInputStream); + try { + this.inputStream.init(this._rawStream, charset, 65535, + Components.interfaces.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER); + } catch(e) { + throw "Translate: setCharacterSet: text encoding not supported"; + } + } else { + Zotero.debug("Translate: setCharacterSet: translate charset override ignored due to BOM or XML parse instruction"); + } + }, + + "read":function(bytes) { + var str = {}; + + if(bytes) { + // read number of bytes requested + var amountRead = this.inputStream.readString(bytes, str); + } else { + // bytes not specified; read a line + this.inputStream.QueryInterface(Components.interfaces.nsIUnicharLineInputStream); + var amountRead = this.inputStream.readLine(str); + } + + if(amountRead) { + return str.value; + } else { + return false; + } + }, + + "getXML":function() { + if(this._mode == "xml/dom") { + return Zotero.Translate.IO.parseDOMXML(this._rawStream, this._charset, this.file.fileSize); + } else { + return new XML(this._readToString().replace(/<\?xml[^>]+\?>/, "")); + } + }, + + "reset":function(newMode) { + this._seekToStart(); + this.inputStream = Components.classes["@mozilla.org/intl/converter-input-stream;1"] + .createInstance(Components.interfaces.nsIConverterInputStream); + this.inputStream.init(this._rawStream, this._charset, 65535, + Components.interfaces.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER); + + this._mode = newMode; + if(Zotero.Translate.IO.rdfDataModes.indexOf(this._mode) !== -1 && !this.RDF) { + this._initRDF(); + } + }, + + "close":function() { + this.inputStream.close(); + } +} + +/******* Write support *******/ + +Zotero.Translate.IO.Write = function(file, mode, charset) { + this._rawStream = Components.classes["@mozilla.org/network/file-output-stream;1"] + .createInstance(Components.interfaces.nsIFileOutputStream); + this._rawStream.init(file, 0x02 | 0x08 | 0x20, 0664, 0); // write, create, truncate + this._writtenToStream = false; + if(mode) this.reset(mode, charset); +} + +Zotero.Translate.IO.Write.prototype = { + "__exposedProps__":["RDF", "write", "setCharacterSet"], + + "_initRDF":function() { + Zotero.debug("Translate: Initializing RDF data store"); + this._dataStore = new Zotero.RDF.AJAW.RDFIndexedFormula(); + this.RDF = new Zotero.Translate.IO._RDFSandbox(this._dataStore); + }, + + "setCharacterSet":function(charset) { + if(typeof charset !== "string") { + throw "Translate: setCharacterSet: charset must be a string"; + } + + this.outputStream = Components.classes["@mozilla.org/intl/converter-output-stream;1"] + .createInstance(Components.interfaces.nsIConverterOutputStream); + if(charset == "UTF-8xBOM") charset = "UTF-8"; + this.outputStream.init(this._rawStream, charset, 1024, "?".charCodeAt(0)); + this._charset = charset; + }, + + "write":function(data) { + if(!this._writtenToStream && this._charset.substr(this._charset.length-4) == "xBOM" + && BOMs[this._charset.substr(0, this._charset.length-4).toUpperCase()]) { + // If stream has not yet been written to, and a UTF type has been selected, write BOM + this._rawStream.write(BOMs[streamCharset], BOMs[streamCharset].length); + } + + if(this._charset == "MACINTOSH") { + // fix buggy Mozilla MacRoman + var splitData = data.split(/([\r\n]+)/); + for(var i=0; i. + + ***** END LICENSE BLOCK ***** +*/ + +/** + * @class Manages the translator sandbox + * @param {Zotero.Translate} translate + * @param {String|window} sandboxLocation + */ +Zotero.Translate.SandboxManager = function(translate, sandboxLocation) { + this.sandbox = {}; + this._translate = translate; +} + +Zotero.Translate.SandboxManager.prototype = { + /** + * Evaluates code in the sandbox + */ + "eval":function(code) { + // eval in sandbox scope + (new Function("with(this) { " + code + " }")).call(this.sandbox); + }, + + /** + * Imports an object into the sandbox + * + * @param {Object} object Object to be imported (under Zotero) + * @param {Boolean} passTranslateAsFirstArgument Whether the translate instance should be passed + * as the first argument to the function. + */ + "importObject":function(object, passAsFirstArgument) { + var translate = this._translate; + + for(var key in (object.__exposedProps__ ? object.__exposedProps__ : object)) { + var fn = (function(object, key) { return object[key] })(); + + // magic "this"-preserving wrapping closure + this.sandbox[key] = function() { + var args = (passAsFirstArgument ? [passAsFirstArgument] : []); + for(var i=0; i. + + ***** END LICENSE BLOCK ***** +*/ + +Zotero.Translate.Item = { + "saveItem":function (translate, item) { + + } +} \ No newline at end of file diff --git a/chrome/content/zotero/xpcom/translation/item_local.js b/chrome/content/zotero/xpcom/translation/item_local.js new file mode 100644 index 0000000000..b0e6f07225 --- /dev/null +++ b/chrome/content/zotero/xpcom/translation/item_local.js @@ -0,0 +1,732 @@ +/* + ***** BEGIN LICENSE BLOCK ***** + + Copyright © 2009 Center for History and New Media + George Mason University, Fairfax, Virginia, USA + http://zotero.org + + This file is part of Zotero. + + Zotero is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Zotero is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Zotero. If not, see . + + ***** END LICENSE BLOCK ***** +*/ + +Zotero.Translate.ItemSaver = function(libraryID, attachmentMode, forceTagType) { + // initialize constants + this.newItems = []; + this.newCollections = []; + this._IDMap = {}; + + // determine library ID + if(libraryID === false) { + this._libraryID = false; + } else if(libraryID === true || libraryID == undefined) { + this._libraryID = null; + } else { + this._libraryID = libraryID; + } + + // determine whether to save files and attachments + if (attachmentMode == Zotero.Translate.ItemSaver.ATTACHMENT_MODE_DOWNLOAD) { + this._saveAttachment = this._saveAttachmentDownload; + } else if(attachmentMode == Zotero.Translate.ItemSaver.ATTACHMENT_MODE_FILE) { + this._saveAttachment = this._saveAttachmentFile; + } else { + this._saveAttachment = function() {}; + } + + this._saveFiles = !(attachmentMode === 0); + + // If group filesEditable==false, don't save attachments + if (typeof this._libraryID == 'number') { + var type = Zotero.Libraries.getType(this._libraryID); + switch (type) { + case 'group': + var groupID = Zotero.Groups.getGroupIDFromLibraryID(this._libraryID); + var group = Zotero.Groups.get(groupID); + if (!group.filesEditable) { + this._saveFiles = false; + } + break; + } + } + + // force tag types if requested + this._forceTagType = forceTagType; +}; + +Zotero.Translate.ItemSaver.ATTACHMENT_MODE_IGNORE = 0; +Zotero.Translate.ItemSaver.ATTACHMENT_MODE_DOWNLOAD = 1; +Zotero.Translate.ItemSaver.ATTACHMENT_MODE_FILE = 2; + +Zotero.Translate.ItemSaver.prototype = { + "saveItem":function(item) { + // Get typeID, defaulting to "webpage" + var type = (item.itemType ? item.itemType : "webpage"); + + if(type == "note") { // handle notes differently + var newItem = new Zotero.Item('note'); + newItem.libraryID = this._libraryID; + newItem.setNote(item.note); + var myID = newItem.save(); + var newItem = Zotero.Items.get(myID); + } else { + if(type == "attachment") { // handle attachments differently + var newItem = this._saveAttachment(item); + } else { + var typeID = Zotero.ItemTypes.getID(type); + var newItem = new Zotero.Item(typeID); + newItem._libraryID = this._libraryID; + } + + this._saveFields(item, newItem); + + // handle creators + if(item.creators) { + this._saveCreators(item, newItem); + } + + // save item + var myID = newItem.save(); + newItem = Zotero.Items.get(myID); + + // handle notes + if(item.notes) { + this._saveNotes(item, myID); + } + + // handle attachments + if(item.attachments) { + for(var i=0; i. + + ***** END LICENSE BLOCK ***** +*/ + +// Byte order marks for various character sets + +/* + * Zotero.Translate: a class for translation of Zotero metadata from and to + * other formats + * + * type can be: + * export + * import + * web + * search + * + * a typical export process: + * var translatorObj = new Zotero.Translate(); + * var possibleTranslators = translatorObj.getTranslators(); + * // do something involving nsIFilePicker; remember, each possibleTranslator + * // object has properties translatorID, label, and targetID + * translatorObj.setLocation(myNsILocalFile); + * translatorObj.setTranslator(possibleTranslators[x]); // also accepts only an ID + * translatorObj.setHandler("done", _translationDone); + * translatorObj.translate(); + * + * + * PUBLIC PROPERTIES: + * @property {Zotero.Connector.CookieManager} cookieManager + * A CookieManager to manage cookies for this Translate instance. + * @property {String} type The type of translator. This is deprecated; use instanceof instead. + * translator - the translator currently in use (read-only; set with + * setTranslator) + * location - the location of the target (read-only; set with setLocation) + * for import/export - this is an instance of nsILocalFile + * for web - this is a URL + * items - items (in Zotero.Item format) to be exported. if this is empty, + * Zotero will export all items in the library (read-only; set with + * setItems). setting items disables export of collections. + * @property {String} path The path or URI of the target + * @property {String} libraryID libraryID (e.g., of a group) of saved database items. null for local + * items or false if items should not be saved at all. defaults to null; set using second + * argument of constructor. + * @property {String} newItems Items created when translate() was called + * @property {String} newCollections Collections created when translate() was called + * @property {Boolean} saveAttachments Whether attachments should be saved + * @property {Boolean} saveFiles Whether files should be saved + * + * PSEUDO-PRIVATE PROPERTIES (used only by other objects in this file): + * + * waitForCompletion - whether to wait for asynchronous completion, or return + * immediately when script has finished executing + * configOptions - options set by translator modifying behavior of + * Zotero.Translate + * displayOptions - options available to user for this specific translator + * + * PRIVATE PROPERTIES: + * + * _charset - character set + * _numericTypes - possible numeric types as a comma-delimited string + * _handlers - handlers for various events (see setHandler) + * _sandbox - sandbox in which translators will be executed + * _streams - streams that need to be closed when execution is complete + * _IDMap - a map from IDs as specified in Zotero.Item() to IDs of actual items + * _parentTranslator - set when a translator is called from another translator. + * among other things, disables passing of the translate + * object to handlers and modifies complete() function on + * returned items + * @param _storage - the stored string to be treated as input + * _storageLength - the length of the stored string + * _exportFileDirectory - the directory to which files will be exported + * + * WEB-ONLY PROPERTIES: + * + * locationIsProxied - whether the URL being scraped is going through + * an EZProxy + * _downloadAssociatedFiles - whether to download content, according to + * preferences + * + * EXPORT-ONLY PROPERTIES: + * + * output - export output (if no location has been specified) + */ +Zotero.Translate = function(type) { + Zotero.debug("Translate: WARNING: new Zotero.Translate() is deprecated; please don't use this if you don't have to"); + // hack + var translate = Zotero.Translate.new(type); + for(var i in translate) { + this[i] = translate[i]; + } + this.constructor = translate.constructor; + this.__proto__ = translate.__proto__; +} + +Zotero.Translate.new = function(type) { + return new Zotero.Translate[type[0].toUpperCase()+type.substr(1).toLowerCase()]; +} + +Zotero.Translate.Sandbox = { + "_inheritFromBase":function(sandboxToMerge) { + var newSandbox = {}; + + for(var method in Zotero.Translate.Sandbox.Base) { + newSandbox[method] = Zotero.Translate.Sandbox.Base[method]; + } + + for(var method in sandboxToMerge) { + newSandbox[method] = sandboxToMerge[method]; + } + + return newSandbox; + }, + + "Base": { + /** + * Called as Zotero.Item#complete() from translators to save items to the database. + */ + "_itemDone":function(translate, item) { + Zotero.debug("Translate: Saving item"); + + // warn if itemDone called after translation completed + if(translate._complete) { + Zotero.debug("Translate: WARNING: Zotero.Item#complete() called after Zotero.done(); please fix your code", 2); + } + + // if we're not supposed to save the item or we're in a child translator, + // just return the item array + if(translate._libraryID === false || translate._parentTranslator) { + translate.newItems.push(item); + + // if a parent sandbox exists, use complete() function from that sandbox + if(translate._parentTranslator) { + if(Zotero.isFx4) { + // XOWs would break this otherwise + item.complete = function() { translate._parentTranslator.Sandbox._itemDone(translate._parentTranslator, item) }; + } else { + // SecurityManager vetos the Fx4 in Fx3.6 code for reasons I don't understand + item.complete = translate._parentTranslator._sandboxManager.sandbox.Zotero.Item.prototype.complete; + } + Zotero.debug("Translate: Calling itemDone from parent sandbox", 4); + } + translate._runHandler("itemDone", item); + return; + } + + var newItem = translate._itemSaver.saveItem(item); + + // Allow progress meter to update + // + // This can probably be re-enabled for web translators once badly asynced ones are fixed + if (translate instanceof Zotero.Translate.Import || translate instanceof Zotero.Translate.Export) { + Zotero.wait(); + } + + translate._runHandler("itemDone", newItem); + }, + + /** + * Gets translator options that were defined in displayOptions in translator header + * + * @param {String} option Option to be retrieved + */ + "getOption":function(translate, option) { + if(typeof option !== "string") { + throw("Translate: getOption: option must be a string"); + return; + } + + return translate._displayOptions[option]; + }, + + /** + * For loading other translators and accessing their methods + * + * @param {Zotero.Translate} translate + * @param {String} type Translator type ("web", "import", "export", or "search") + * @returns {Object} A safeTranslator object, which operates mostly like Zotero.Translate + */ + "loadTranslator":function(translate, type) { + const setDefaultHandlers = function(translate, translation) { + var noHandlers = true; + for(var i in translation._handlers) { + noHandlers = false; + break; + } + if(noHandlers) { + if(!(translation instanceof Zotero.Translate.Export)) { + translation.setHandler("itemDone", function(obj, item) { item.complete() }); + } + if(translation instanceof Zotero.Translate.Web) { + translation.setHandler("selectItems", translate._handlers["selectItems"]); + } + } + } + + if(typeof type !== "string") { + throw("Translate: loadTranslator: type must be a string"); + return; + } + + Zotero.debug("Translate: creating translate instance of type "+type+" in sandbox"); + var translation = Zotero.Translate.new(type); + translation._parentTranslator = translate; + + if(translation instanceof Zotero.Translate.Export && !(translation instanceof Zotero.Translate.Export)) { + throw("Translate: only export translators may call other export translators"); + } + + // for security reasons, safeTranslator wraps the translator object. + // note that setLocation() is not allowed + var safeTranslator = new Object(); + safeTranslator.setSearch = function(arg) { return translation.setSearch(arg) }; + safeTranslator.setDocument = function(arg) { return translation.setDocument(arg) }; + safeTranslator.setHandler = function(arg1, arg2) { + translation.setHandler(arg1, + function(obj, item) { + try { + if(Zotero.isFx4 && (this instanceof Zotero.Translate.Web || this instanceof Zotero.Translate.Search)) { + // item is wrapped in an XPCCrossOriginWrapper that we can't get rid of + // except by making a deep copy. seems to be due to + // https://bugzilla.mozilla.org/show_bug.cgi?id=580128 + // hear that? that's the sound of me banging my head against the wall. + // if there is no better way to do this soon, i am going to need a + // brain transplant... + var unwrappedItem = JSON.parse(JSON.stringify(item)); + unwrappedItem.complete = item.complete; + } else { + var unwrappedItem = item; + } + + arg2(obj, unwrappedItem); + } catch(e) { + translate.complete(false, e); + } + } + ); + }; + safeTranslator.setString = function(arg) { translation.setString(arg) }; + safeTranslator.setTranslator = function(arg) { return translation.setTranslator(arg) }; + safeTranslator.getTranslators = function() { return translation.getTranslators() }; + safeTranslator.translate = function() { + setDefaultHandlers(translate, translation); + return translation.translate(false); + }; + safeTranslator.getTranslatorObject = function() { + translation._loadTranslator(); + translation._prepareTranslation(); + setDefaultHandlers(translate, translation); + + // return sandbox + return translation.sandboxManager.sandbox; + }; + + // TODO security is not super-tight here, as someone could pass something into arg + // that gets evaluated in the wrong scope in Fx < 4. We should wrap this. + + return safeTranslator; + }, + + /** + * Enables asynchronous detection or translation + * @param {Zotero.Translate} translate + */ + "wait":function(translate) { + if(translate._currentState == "translate" || translate instanceof Zotero.Translate.Web) { + translate._waitForCompletion = true; + } else { + throw "Translate: cannot call Zotero.wait() in detectCode of non-web translators"; + } + }, + + /** + * Completes asynchronous detection or translation + * + * @param {Zotero.Translate} translate + * @param {Boolean|String} [val] Whether detection or translation completed successfully. + * For detection, this should be a string or false. For translation, this should be + * boolean. + * @param {String} [error] The error string, if an error occurred. + */ + "done":function(translate, val, error) { + me.complete(typeof val === "undefined" ? true : val, (error ? error : "No error message specified")); + }, + + /** + * Proxy for translator _debug function + * + * @param {Zotero.Translate} translate + * @param {String} string String to write to console + * @param {String} [level] Level to log as (1 to 5) + */ + "debug":function(translate, string, level) { + translate._debug(string, level); + } + }, + + /** + * Web functions exposed to sandbox + * @namespace + */ + "Web":{ + /** + * Lets user pick which items s/he wants to put in his/her library + */ + "selectItems":function(translate, options) { + // hack to see if there are options + var haveOptions = false; + for(var i in options) { + haveOptions = true; + break; + } + + if(!haveOptions) { + throw "Translate: translator called select items with no items"; + } + + if(translate._handlers.select) { + return translate._runHandler("select", options); + } else { // no handler defined; assume they want all of them + return options; + } + }, + + /** + * Overloads {@link Zotero.Translate.Sandbox.Base._itemDone} + */ + "_itemDone":function(translate, item) { + if(!item.itemType) { + item.itemType = "webpage"; + Zotero.debug("Translate: WARNING: No item type specified"); + } + + if(item.type == "attachment" || item.type == "note") { + Zotero.debug("Translate: Discarding standalone "+item.type+" in non-import translator", 2); + return; + } + + // store library catalog if this item was captured from a website, and + // libraryCatalog is truly undefined (not false or "") + if(item.repository !== undefined) { + Zotero.debug("Translate: 'repository' field is now 'libraryCatalog'; please fix your code", 2); + item.libraryCatalog = item.repository; + delete item.repository; + } + + // automatically set library catalog + if(item.libraryCatalog === undefined) { + item.libraryCatalog = translate.translator[0].label; + } + + // automatically set access date if URL is set + if(item.url && typeof item.accessDate == 'undefined') { + item.accessDate = "CURRENT_TIMESTAMP"; + } + + // create short title + if(item.shortTitle === undefined && Zotero.Utilities.fieldIsValidForType("shortTitle", item.itemType)) { + // only set if changes have been made + var setShortTitle = false; + var title = item.title; + + // shorten to before first colon + var index = title.indexOf(":"); + if(index !== -1) { + title = title.substr(0, index); + setShortTitle = true; + } + // shorten to after first question mark + index = title.indexOf("?"); + if(index !== -1) { + index++; + if(index != title.length) { + title = title.substr(0, index); + setShortTitle = true; + } + } + + if(setShortTitle) item.shortTitle = title; + } + + // call super + Zotero.Translate.Sandbox.Base._itemDone(translate, item); + } + }, + + /** + * Import functions exposed to sandbox + * @namespace + */ + "Import":{ + /** + * Saves a collection to the DB + * Called as collection.complete() from the sandbox + * @param {Object} collection + */ + "_collectionDone":function(translate, collection) { + var collection = translate._itemSaver.saveCollection(collection); + translate._runHandler("collectionDone", collection); + } + }, + + /** + * Export functions exposed to sandbox + * @namespace + */ + "Export":{ + "nextItem":function(translate) { + var item = translate._itemGetter.nextItem(); + translate._runHandler("itemDone", item); + Zotero.wait(); + return item; + }, + + "nextCollection":function(translate) { + if(!translate.translator[0].configOptions.getCollections) { + throw("Translate: getCollections configure option not set; cannot retrieve collection"); + } + + return translate._itemGetter.nextCollection(); + } + }, + + /** + * Search functions exposed to sandbox + * @namespace + */ + "Search":{ + /** + * @borrows Zotero.Translate.Sandbox.Web._itemDone as this._itemDone + */ + "_itemDone":function(translate, item) { + Zotero.Translate.Sandbox.Web.itemDone(translate, item); + } + } +} + +/** + * @class Base class for all translators + */ +Zotero.Translate.Base = function() {} +Zotero.Translate.Base.prototype = { + "init":function() { + this._handlers = []; + this._currentState = null; + this.document = null; + this.location = null; + }, + + /** + * Sets the location to operate upon + * + * @param {String|nsIFile} location The URL to which the sandbox should be bound or path to local file + */ + "setLocation":function(location) { + this.location = location; + if(typeof this.location == "object") { // if a file + this.path = location.path; + } else { // if a url + this.path = location; + } + }, + + /** + * Sets the translator to be used for import/export + * + * @param {Zotero.Translator|string} Translator object or ID + */ + "setTranslator":function(translator) { + if(!translator) { + throw("cannot set translator: invalid value"); + } + + this.translator = null; + this._setDisplayOptions = null; + + if(typeof(translator) == "object") { // passed an object and not an ID + if(translator.translatorID) { + this.translator = [translator]; + } else { + throw("No translatorID specified"); + } + } else { + this.translator = [Zotero.Translators.get(translator)]; + } + + return !!this.translator; + }, + + /** + * Registers a handler function to be called when translation is complete + * + * @param {String} type Type of handler to register. Legal values are: + * select + * valid: web + * called: when the user needs to select from a list of available items + * passed: an associative array in the form id => text + * returns: a numerically indexed array of ids, as extracted from the passed + * string + * itemDone + * valid: import, web, search + * called: when an item has been processed; may be called asynchronously + * passed: an item object (see Zotero.Item) + * returns: N/A + * collectionDone + * valid: import + * called: when a collection has been processed, after all items have been + * added; may be called asynchronously + * passed: a collection object (see Zotero.Collection) + * returns: N/A + * done + * valid: all + * called: when all processing is finished + * passed: true if successful, false if an error occurred + * returns: N/A + * debug + * valid: all + * called: when Zotero.debug() is called + * passed: string debug message + * returns: true if message should be logged to the console, false if not + * error + * valid: all + * called: when a fatal error occurs + * passed: error object (or string) + * returns: N/A + * translators + * valid: all + * called: when a translator search initiated with Zotero.Translate.getTranslators() is + * complete + * passed: an array of appropriate translators + * returns: N/A + * @param {Function} handler Callback function. All handlers will be passed the current + * translate instance as the first argument. The second argument is dependent on the handler. + */ + "setHandler":function(type, handler) { + if(!this._handlers[type]) { + this._handlers[type] = new Array(); + } + this._handlers[type].push(handler); + }, + + /* + * Clears all handlers for a given function + * @param {String} type See {@link Zotero.Translate.Base#setHandler} for valid values + */ + "clearHandlers":function(type) { + this._handlers[type] = new Array(); + }, + + /** + * Clears all handlers for a given function + * @param {String} type See {@link Zotero.Translate.Base#setHandler} for valid values + * @param {Any} argument Argument to be passed to handler + */ + "_runHandler":function(type, argument) { + var returnValue = undefined; + if(this._handlers[type]) { + for(var i in this._handlers[type]) { + Zotero.debug("Translate: running handler "+i+" for "+type, 5); + try { + if(this._parentTranslator) { + returnValue = this._handlers[type][i](null, argument); + } else { + returnValue = this._handlers[type][i](this, argument); + } + } catch(e) { + if(this._parentTranslator) { + // throw handler errors if they occur when a translator is + // called from another translator, so that the + // "Could Not Translate" dialog will appear if necessary + throw(e); + } else { + // otherwise, fail silently, so as not to interfere with + // interface cleanup + Zotero.debug("Translate: "+e+' in handler '+i+' for '+type, 5); + } + } + } + } + return returnValue; + }, + + /** + * Gets all applicable translators of a given type + * + * For import, you should call this after setLocation; otherwise, you'll just get a list of all + * import filters, not filters equipped to handle a specific file + * + * @return {Zotero.Translator[]} An array of {@link Zotero.Translator} objects + */ + "getTranslators":function(getAllTranslators) { + // do not allow simultaneous instances of getTranslators + if(this._currentState == "detect") throw "Translate: getTranslators: detection is already running"; + this._currentState = "detect"; + this._getAllTranslators = getAllTranslators; + this._potentialTranslators = this._getPotentialTranslators(); + this._foundTranslators = []; + + Zotero.debug("Translate: Searching for translators for "+(this.path ? this.path : "an undisclosed location"), 3); + + this._detect(); + + // if detection returns immediately, return found translators + if(!this._currentState) return this._foundTranslators; + }, + + /** + * does the actual translation + * + * @param {NULL|Integer|FALSE} [libraryID=null] Library in which to save items, + * or NULL for default library; + * if FALSE, don't save items + * @param {Boolean} [saveAttachments=true] Exclude attachments (e.g., snapshots) on import + */ + "translate":function(libraryID, saveAttachments) { + // initialize properties specific to each translation + this._currentState = "translate"; + + if(!this.translator || !this.translator.length) { + throw("Translate: Failed: no translator specified"); + } + + // load translators + if(!this._loadTranslator(this.translator[0])) return; + + // set display options to default if they don't exist + if(!this._displayOptions) this._displayOptions = this.translator[0]._displayOptions; + + // prepare translation + this._prepareTranslation(libraryID, typeof saveAttachments === "undefined" ? true : saveAttachments); + + Zotero.debug("Translate: Beginning translation with "+this.translator[0].label); + + // translate + try { + this._sandboxManager.sandbox["do"+this._entryFunctionSuffix](this.document, this.location); + } catch(e) { + if(this._parentTranslator) { + throw(e); + } else { + this.complete(false, e); + return false; + } + } + + if(!this._waitForCompletion) this.complete(true); + }, + + /** + * Executed on translator completion, either automatically from a synchronous scraper or as + * done() from an asynchronous scraper + * + * Finishes things up and calls callback function(s) + */ + "complete":function(returnValue, error) { + // Make sure this isn't called twice + if(this._currentState === null) { + Zotero.debug("Translate: WARNING: Zotero.done() called after translation completion; please fix your code"); + return; + } + + var errorString = null; + if(!returnValue) errorString = this._generateErrorString(error); + + if(this._currentState === "detect") { + if(this._potentialTranslators.length) { + var lastTranslator = this._potentialTranslators.shift(); + + if(returnValue) { + var dupeTranslator = {"itemType":returnValue}; + for(var i in lastTranslator) dupeTranslator[i] = lastTranslator[i]; + this._foundTranslators.push(dupeTranslator); + } else if(error) { + this._debug("Detect using "+lastTranslator.label+" failed: \n"+errorString, 2); + } + } + + if(this._potentialTranslators.length && (this._getAllTranslators || !returnValue)) { + // more translators to try; proceed to next translator + this._detect(); + } else { + this._runHandler("translators", this._foundTranslators ? this._foundTranslators : false); + } + } else { + if(returnValue) { + this._debug("Translation successful"); + } else { + // report error to console + if(this.translator[0] && this.translator[0].logError) { + this.translator[0].logError(error.toString(), "exception"); + } else { + Zotero.logError(error); + } + + // report error to debug log + this._debug("Translation using "+(this.translator && this.translator[0] && this.translator[0].label ? this.translator[0].label : "no translator")+" failed: \n"+errorString, 2); + + this._runHandler("error", error); + } + + // call handlers + this._runHandler("done", returnValue); + } + + this._waitForCompletion = false; + this._currentState = null; + return errorString; + }, + + /** + * Runs detect code for a translator + */ + "_detect":function() { + if(!this._loadTranslator(this._potentialTranslators[0])) { + this.complete(false, "Error loading translator into sandbox"); + return; + } + this._prepareDetection(); + + try { + var returnValue = this._sandboxManager.sandbox["detect"+this._entryFunctionSuffix](this.document, this.location); + } catch(e) { + this.complete(false, e); + return; + } + + if(!this._waitForCompletion) this.complete(returnValue); + }, + + /** + * Loads the translator into its sandbox + */ + "_loadTranslator":function(translator) { + if(!this._sandboxManager) this._generateSandbox(); + this._waitForCompletion = false; + + Zotero.debug("Translate: Parsing code for "+translator.label, 4); + + try { + this._sandboxManager.eval("var translatorInfo = "+translator.code, this._sandbox); + return true; + } catch(e) { + if(translator.logError) { + translator.logError(e.toString()); + } else { + Zotero.logError(e); + } + + this.complete(false, "parse error"); + return false; + } + + return true; + }, + + /** + * Generates a sandbox for scraping/scraper detection + */ + "_generateSandbox":function() { + var sandboxLocation = "http://www.example.com/"; + + if(this instanceof Zotero.Translate.Web) { + // use real URL, not proxied version, to create sandbox + sandboxLocation = this.document.defaultView; + } else if(this instanceof Zotero.Translate.Search) { + // generate sandbox for search by extracting domain from translator target + if(this.translator && this.translator[0] && this.translator[0].target) { + // so that web translators work too + const searchSandboxRe = /^http:\/\/[\w.]+\//; + var tempURL = this.translator[0].target.replace(/\\/g, "").replace(/\^/g, ""); + var m = searchSandboxRe.exec(tempURL); + if(m) sandboxLocation = m[0]; + } + } else if(this._parentTranslator) { + sandboxLocation = this._parentTranslator._sandboxLocation; + } + + Zotero.debug("Translate: Binding sandbox to "+(typeof sandboxLocation == "object" ? sandboxLocation.document.location : sandboxLocation), 4); + this._sandboxLocation = sandboxLocation; + this._sandboxManager = new Zotero.Translate.SandboxManager(this, sandboxLocation); + this._sandboxManager.eval("var Zotero = {};"+ + "Zotero.Item = function (itemType) {"+ + "this.itemType = itemType;"+ + "this.creators = [];"+ + "this.notes = [];"+ + "this.tags = [];"+ + "this.seeAlso = [];"+ + "this.attachments = [];"+ + "};"+ + "Zotero.Item.prototype.complete = function() { Zotero._itemDone(this); };"+ + "Zotero.Collection = function () {};"+ + "Zotero.Collection.prototype.complete = function() { Zotero._collectionDone(this); };"); + + this._sandboxManager.importObject(this.Sandbox, this); + this._sandboxManager.importObject({"Utilities":new Zotero.Utilities.Translate(this)}); + this._sandboxManager.sandbox.Zotero.Utilities.HTTP = this._sandboxManager.sandbox.Zotero.Utilities; + }, + + /** + * Logs a debugging message + */ + "_debug":function(string, level) { + if(typeof string === "object") string = new XPCSafeJSObjectWrapper(string); + if(level !== undefined && typeof level !== "number") { + Zotero.debug("debug: level must be an integer"); + return; + } + + // if handler does not return anything explicitly false, show debug + // message in console + if(this._runHandler("debug", string) !== false) { + if(typeof string == "string") string = "Translate: "+string; + Zotero.debug(string, level); + } + }, + + "_generateErrorString":function(error) { + var errorString = ""; + if(typeof(error) == "string") { + errorString = "\nthrown exception => "+error; + } else { + for(var i in error) { + if(typeof(error[i]) != "object") { + errorString += "\n"+i+' => '+error[i]; + } + } + } + + errorString += "\nurl => "+this.path + + "\ndownloadAssociatedFiles => "+Zotero.Prefs.get("downloadAssociatedFiles") + + "\nautomaticSnapshots => "+Zotero.Prefs.get("automaticSnapshots"); + return errorString.substr(1); + }, + + /** + * No-op for preparing detection + */ + "_prepareDetection":function() {}, + + /** + * No-op for preparing translation + */ + "_prepareTranslation":function() {}, + + /** + * Get all potential translators + */ + "_getPotentialTranslators":function() { + return Zotero.Translators.getAllForType(this.type); + } +} + +/** + * @property {Document} document The document object to be used for web scraping (set with setDocument) + */ +Zotero.Translate.Web = function() { + this.init(); +} +Zotero.Translate.Web.prototype = new Zotero.Translate.Base(); +Zotero.Translate.Web.prototype.type = "web"; +Zotero.Translate.Web.prototype._entryFunctionSuffix = "Web"; +Zotero.Translate.Web.prototype.Sandbox = Zotero.Translate.Sandbox._inheritFromBase(Zotero.Translate.Sandbox.Web); + +/** + * Sets the browser to be used for web translation + * @param {Document} doc An HTML document + */ +Zotero.Translate.Web.prototype.setDocument = function(doc) { + this.document = doc; + this.setLocation(doc.location.href); +} + +/** + * Sets a Zotero.Connector.CookieManager to handle cookie management for XHRs initiated from this + * translate instance + * + * @param {Zotero.Connector.CookieManager} cookieManager + */ +Zotero.Translate.Web.prototype.setCookieManager = function(cookieManager) { + this.cookieManager = cookieManager; +} + +/** + * Sets the location to operate upon + * + * @param {String} location The URL of the page to translate + */ +Zotero.Translate.Web.prototype.setLocation = function(location) { + // account for proxies + this.location = Zotero.Proxies.proxyToProper(location); + if(this.location != location) { + // figure out if this URL is being proxies + this.locationIsProxied = true; + } + this.path = this.location; +} + +/** + * Get all potential translators + */ +Zotero.Translate.Web.prototype._getPotentialTranslators = function() { + var allTranslators = Zotero.Translators.getAllForType("web"); + var potentialTranslators = []; + + for(var i=0; i 1) { + this.translator.shift(); + this.translate(this._libraryID, this.saveAttachments); + return; + } + } + + // call super + Zotero.Translate.Base.prototype.complete.apply(this, [returnValue, error]); +} + +/** + * Loads the translator into its sandbox. We overload this to make sure that the sandbox is + * bound to the correct URI. + */ +Zotero.Translate.Search.prototype._loadTranslator = function() { + this._generateSandbox(); + Zotero.Translate.Base.prototype._loadTranslator.apply(this); +} + +Zotero.Translate.Search.prototype._prepareTranslation = Zotero.Translate.Web.prototype._prepareTranslation; + +/** + * IO-related functions + * @namespace + */ +Zotero.Translate.IO = { + /** + * Parses XML using DOMParser + */ + "parseDOMXML":function(input, charset, size) { + try { + var dp = new DOMParser(); + } catch(e) { + try { + var dp = Components.classes["@mozilla.org/xmlextras/domparser;1"] + .createInstance(Components.interfaces.nsIDOMParser); + } catch(e) { + throw "DOMParser not supported"; + } + } + + if(typeof input == "string") { + var nodes = dp.parseFromString(input, "text/xml"); + } else { + var nodes = dp.parseFromStream(input, charset, size, "text/xml"); + } + + if(nodes.getElementsByTagName("parsererror").length) { + throw("DOMParser error: loading data into data store failed"); + } + + return nodes; + }, + + /** + * Names of RDF data modes + */ + "rdfDataModes":["rdf", "rdf/xml", "rdf/n3"] +}; + +/******* String support *******/ + +Zotero.Translate.IO.String = function(string, uri, mode) { + if(string && typeof string === "string") { + this.string = string; + } else { + this.string = ""; + } + this._stringPointer = 0; + this._uri = uri; + + if(mode) { + this.reset(mode); + } +} + +Zotero.Translate.IO.String.prototype = { + "__exposedProps__":["getXML", "RDF", "read", "write", "setCharacterSet", "getXML"], + + "_initRDF":function() { + Zotero.debug("Translate: Initializing RDF data store"); + this._dataStore = new Zotero.RDF.AJAW.RDFIndexedFormula(); + + if(this.string.length) { + var parser = new Zotero.RDF.AJAW.RDFParser(this._dataStore); + parser.parse(Zotero.Translate.IO.parseDOMXML(this.string), this._uri); + } + + this.RDF = new Zotero.Translate.IO._RDFSandbox(this._dataStore); + }, + + "setCharacterSet":function(charset) {}, + + "read":function(bytes) { + // if we are reading in RDF data mode and no string is set, serialize current RDF to the + // string + if(Zotero.Translate.IO.rdfDataModes.indexOf(this._mode) !== -1 && this.string === "") { + this.string = this.RDF.serialize(); + } + + // return false if string has been read + if(this._stringPointer >= this.string.length) { + return false; + } + + if(bytes !== undefined) { + if(this._stringPointer >= this.string.length) return false; + var oldPointer = this._stringPointer; + this._stringPointer += bytes; + return this.string.substr(oldPointer, bytes); + } else { + // bytes not specified; read a line + var oldPointer = this._stringPointer; + var lfIndex = this.string.indexOf("\n", this._stringPointer); + + if(lfIndex != -1) { + // in case we have a CRLF + this._stringPointer = lfIndex+1; + if(this.string.length > lfIndex && this.string[lfIndex-1] == "\r") { + lfIndex--; + } + return this.string.substr(oldPointer, lfIndex-oldPointer); + } + + var crIndex = this.string.indexOf("\r", this._stringPointer); + if(crIndex != -1) { + this._stringPointer = crIndex+1; + return this.string.substr(oldPointer, crIndex-oldPointer-1); + } + + this._stringPointer = this.string.length; + return this.string.substr(oldPointer); + } + }, + + "write":function(data) { + this.string += data; + this.string.length += data.length; + }, + + "getXML":function() { + if(this._mode == "xml/dom") { + return Zotero.Translate.IO.parseDOMXML(this.string); + } else { + return new XML(this.string.replace(/<\?xml[^>]+\?>/, "")); + } + }, + + "reset":function(newMode) { + this._stringPointer = 0; + + this._mode = newMode; + if(Zotero.Translate.IO.rdfDataModes.indexOf(this._mode) !== -1) { + this._initRDF(); + } + }, + + "close":function() {} +} + +/****** RDF DATA MODE ******/ + +/** + * @class An API for handling RDF from the sandbox. This is exposed to translators as Zotero.RDF. + * + * @property {Zotero.RDF.AJAW.RDFIndexedFormula} _dataStore + * @property {Integer[]} _containerCounts + * @param {Zotero.RDF.AJAW.RDFIndexedFormula} dataStore + */ +Zotero.Translate.IO._RDFSandbox = function(dataStore) { + this._dataStore = dataStore; +} + +Zotero.Translate.IO._RDFSandbox.prototype = { + "_containerCounts":[], + "__exposedProps__":["addStatement", "newResource", "newContainer", "addContainerElement", + "getContainerElements", "addNamespace", "getAllResources", "getResourceURI", "getArcsIn", + "getArcsOut", "getSources", "getTargets", "getStatementsMatching"], + + /** + * Gets a resource as a Zotero.RDF.AJAW.RDFSymbol, rather than a string + * @param {String|Zotero.RDF.AJAW.RDFSymbol} about + * @return {Zotero.RDF.AJAW.RDFSymbol} + */ + "_getResource":function(about) { + return (typeof about == "object" ? about : new Zotero.RDF.AJAW.RDFSymbol(about)); + }, + + /** + * Runs a callback to initialize this RDF store + */ + "_init":function() { + if(this._prepFunction) { + this._dataStore = this._prepFunction(); + delete this._prepFunction; + } + }, + + /** + * Serializes the current RDF to a string + */ + "serialize":function(dataMode) { + var serializer = Serializer(); + + for(var prefix in this._dataStore.namespaces) { + serializer.suggestPrefix(prefix, this._dataStore.namespaces[prefix]); + } + + // serialize in appropriate format + if(dataMode == "rdf/n3") { + return serializer.statementsToN3(this._dataStore.statements); + } + + return serializer.statementsToXML(this._dataStore.statements); + }, + + /** + * Adds an RDF triple + * @param {String|Zotero.RDF.AJAW.RDFSymbol} about + * @param {String|Zotero.RDF.AJAW.RDFSymbol} relation + * @param {String|Zotero.RDF.AJAW.RDFSymbol} value + * @param {Boolean} literal Whether value should be treated as a literal (true) or a resource + * (false) + */ + "addStatement":function(about, relation, value, literal) { + this + + if(literal) { + // zap chars that Mozilla will mangle + value = value.toString().replace(/[\x00-\x08\x0B\x0C\x0E-\x1F]/g, ''); + } else { + value = this._getResource(value); + } + + this._dataStore.add(this._getResource(about), this._getResource(relation), value); + }, + + /** + * Creates a new anonymous resource + * @return {Zotero.RDF.AJAW.RDFSymbol} + */ + "newResource":function() { + return new Zotero.RDF.AJAW.RDFBlankNode(); + }, + + /** + * Creates a new container resource + * @param {String} type The type of the container ("bag", "seq", or "alt") + * @param {String|Zotero.RDF.AJAW.RDFSymbol} about The URI of the resource + * @return {Zotero.Translate.RDF.prototype.newContainer + */ + "newContainer":function(type, about) { + const rdf = "http://www.w3.org/1999/02/22-rdf-syntax-ns#"; + const containerTypes = {"bag":"Bag", "seq":"Seq", "alt":"Alt"}; + + type = type.toLowerCase(); + if(!containerTypes[type]) { + throw "Invalid container type in Zotero.RDF.newContainer"; + } + + var about = this._getResource(about); + this.addStatement(about, rdf+"type", rdf+containerTypes[type], false); + this._containerCounts[about.toNT()] = 1; + + return about; + }, + + /** + * Adds a new element to a container + * @param {String|Zotero.RDF.AJAW.RDFSymbol} about The container + * @param {String|Zotero.RDF.AJAW.RDFSymbol} element The element to add to the container + * @param {Boolean} literal Whether element should be treated as a literal (true) or a resource + * (false) + */ + "addContainerElement":function(about, element, literal) { + const rdf = "http://www.w3.org/1999/02/22-rdf-syntax-ns#"; + + var about = this._getResource(about); + this._dataStore.add(about, new Zotero.RDF.AJAW.RDFSymbol(rdf+"_"+(this._containerCounts[about.toNT()]++)), element, literal); + }, + + /** + * Gets all elements within a container + * @param {String|Zotero.RDF.AJAW.RDFSymbol} about The container + * @return {Zotero.RDF.AJAW.RDFSymbol[]} + */ + "getContainerElements":function(about) { + const liPrefix = "http://www.w3.org/1999/02/22-rdf-syntax-ns#_"; + + var about = this._getResource(about); + var statements = this._dataStore.statementsMatching(about); + var containerElements = []; + + // loop over arcs out looking for list items + for(var i=0; i. + + ***** END LICENSE BLOCK ***** +*/ + +// Enumeration of types of translators +const TRANSLATOR_TYPES = {"import":1, "export":2, "web":4, "search":8}; + +/** + * Singleton to handle loading and caching of translators + * @namespace + */ +Zotero.Translators = new function() { + var _cache, _translators; + var _initialized = false; + + /** + * Initializes translator cache, loading all relevant translators into memory + */ + this.init = function() { + _initialized = true; + + var start = (new Date()).getTime(); + var transactionStarted = false; + + Zotero.UnresponsiveScriptIndicator.disable(); + + // Use try/finally so that we always reset the unresponsive script warning + try { + _cache = {"import":[], "export":[], "web":[], "search":[]}; + _translators = {}; + + var dbCacheResults = Zotero.DB.query("SELECT leafName, translatorJSON, "+ + "code, lastModifiedTime FROM translatorCache"); + var dbCache = {}; + for each(var cacheEntry in dbCacheResults) { + dbCache[cacheEntry.leafName] = cacheEntry; + } + + var i = 0; + var filesInCache = {}; + var contents = Zotero.getTranslatorsDirectory().directoryEntries; + while(contents.hasMoreElements()) { + var file = contents.getNext().QueryInterface(Components.interfaces.nsIFile); + var leafName = file.leafName; + if(!leafName || leafName[0] == ".") continue; + var lastModifiedTime = file.lastModifiedTime; + + var dbCacheEntry = false; + if(dbCache[leafName]) { + filesInCache[leafName] = true; + if(dbCache[leafName].lastModifiedTime == lastModifiedTime) { + dbCacheEntry = dbCache[file.leafName]; + } + } + + if(dbCacheEntry) { + // get JSON from cache if possible + var translator = new Zotero.Translator(file, dbCacheEntry.translatorJSON, dbCacheEntry.code); + filesInCache[leafName] = true; + } else { + // otherwise, load from file + var translator = new Zotero.Translator(file); + } + + if(translator.translatorID) { + if(_translators[translator.translatorID]) { + // same translator is already cached + translator.logError('Translator with ID '+ + translator.translatorID+' already loaded from "'+ + _translators[translator.translatorID].file.leafName+'"'); + } else { + // add to cache + _translators[translator.translatorID] = translator; + for(var type in TRANSLATOR_TYPES) { + if(translator.translatorType & TRANSLATOR_TYPES[type]) { + _cache[type].push(translator); + } + } + + if(!dbCacheEntry) { + // Add cache misses to DB + if(!transactionStarted) { + transactionStarted = true; + Zotero.DB.beginTransaction(); + } + Zotero.Translators.cacheInDB(leafName, translator.metadataString, translator.cacheCode ? translator.code : null, lastModifiedTime); + delete translator.metadataString; + } + } + } + + i++; + } + + // Remove translators from DB as necessary + for(var leafName in dbCache) { + if(!filesInCache[leafName]) { + Zotero.DB.query("DELETE FROM translatorCache WHERE leafName = ?", [leafName]); + } + } + + // Close transaction + if(transactionStarted) { + Zotero.DB.commitTransaction(); + } + + // Sort by priority + var collation = Zotero.getLocaleCollation(); + var cmp = function (a, b) { + if (a.priority > b.priority) { + return 1; + } + else if (a.priority < b.priority) { + return -1; + } + return collation.compareString(1, a.label, b.label); + } + for(var type in _cache) { + _cache[type].sort(cmp); + } + } + finally { + Zotero.UnresponsiveScriptIndicator.enable(); + } + + Zotero.debug("Cached "+i+" translators in "+((new Date()).getTime() - start)+" ms"); + } + + /** + * Gets the translator that corresponds to a given ID + */ + this.get = function(id) { + if(!_initialized) this.init(); + return _translators[id] ? _translators[id] : false; + } + + /** + * Gets all translators for a specific type of translation + */ + this.getAllForType = function(type) { + if(!_initialized) this.init(); + return _cache[type].slice(0); + } + + /** + * @param {String} label + * @return {String} + */ + this.getFileNameFromLabel = function(label) { + return Zotero.File.getValidFileName(label) + ".js"; + } + + + /** + * @param {String} metadata + * @param {String} metadata.translatorID Translator GUID + * @param {Integer} metadata.translatorType See TRANSLATOR_TYPES in translate.js + * @param {String} metadata.label Translator title + * @param {String} metadata.creator Translator author + * @param {String|Null} metadata.target Target regexp + * @param {String|Null} metadata.minVersion + * @param {String} metadata.maxVersion + * @param {Integer} metadata.priority + * @param {Boolean} metadata.inRepository + * @param {String} metadata.lastUpdated SQL date + * @param {String} code + * @return {nsIFile} + */ + this.save = function(metadata, code) { + if (!metadata.translatorID) { + throw ("metadata.translatorID not provided in Zotero.Translators.save()"); + } + + if (!metadata.translatorType) { + var found = false; + for each(var type in TRANSLATOR_TYPES) { + if (metadata.translatorType & type) { + found = true; + break; + } + } + if (!found) { + throw ("Invalid translatorType '" + metadata.translatorType + "' in Zotero.Translators.save()"); + } + } + + if (!metadata.label) { + throw ("metadata.label not provided in Zotero.Translators.save()"); + } + + if (!metadata.priority) { + throw ("metadata.priority not provided in Zotero.Translators.save()"); + } + + if (!metadata.lastUpdated) { + throw ("metadata.lastUpdated not provided in Zotero.Translators.save()"); + } + + if (!code) { + throw ("code not provided in Zotero.Translators.save()"); + } + + var fileName = Zotero.Translators.getFileNameFromLabel(metadata.label); + var destFile = Zotero.getTranslatorsDirectory(); + destFile.append(fileName); + + var metadataJSON; + + // JSON.stringify (FF 3.5.4 and up) has the benefit of indenting JSON + if (typeof JSON != "undefined" && 'function' == typeof JSON.stringify) { + metadataJSON = JSON.stringify(metadata,null,8); + } else { + var nsIJSON = Cc["@mozilla.org/dom/json;1"].createInstance(Ci.nsIJSON); + metadataJSON = nsIJSON.encode(metadata); + } + + var str = metadataJSON + "\n\n" + code; + + var translator = Zotero.Translators.get(metadata.translatorID); + if (translator && destFile.equals(translator.file)) { + var sameFile = true; + } + + if (!sameFile && destFile.exists()) { + var msg = "Overwriting translator with same filename '" + + fileName + "'"; + Zotero.debug(msg, 1); + Zotero.debug(metadata, 1); + Components.utils.reportError(msg + " in Zotero.Translators.save()"); + } + + if (translator && translator.file.exists()) { + translator.file.remove(false); + } + + Zotero.debug("Saving translator '" + metadata.label + "'"); + Zotero.debug(str); + Zotero.File.putContents(destFile, str); + + return destFile; + } + + this.cacheInDB = function(fileName, metadataJSON, code, lastModifiedTime) { + Zotero.DB.query("REPLACE INTO translatorCache VALUES (?, ?, ?, ?)", + [fileName, metadataJSON, code, lastModifiedTime]); + } +} + +/** + * @class Represents an individual translator + * @constructor + * @param {nsIFile} file File from which to generate a translator object + * @property {String} translatorID Unique GUID of the translator + * @property {Integer} translatorType Type of the translator (use bitwise & with TRANSLATOR_TYPES to read) + * @property {String} label Human-readable name of the translator + * @property {String} creator Author(s) of the translator + * @property {String} target Location that the translator processes + * @property {String} minVersion Minimum Zotero version + * @property {String} maxVersion Minimum Zotero version + * @property {Integer} priority Lower-priority translators will be selected first + * @property {String} browserSupport String indicating browser supported by the translator + * g = Gecko (Firefox) + * c = Google Chrome (WebKit & V8) + * s = Safari (WebKit & Nitro/Squirrelfish Extreme) + * i = Internet Explorer + * @property {Object} configOptions Configuration options for import/export + * @property {Object} displayOptions Display options for export + * @property {Boolean} inRepository Whether the translator may be found in the repository + * @property {String} lastUpdated SQL-style date and time of translator's last update + * @property {String} code The executable JavaScript for the translator + */ +Zotero.Translator = function(file, json, code) { + const codeGetterFunction = function() { return Zotero.File.getContents(this.file); } + // Maximum length for the info JSON in a translator + const MAX_INFO_LENGTH = 4096; + const infoRe = /^\s*{[\S\s]*?}\s*?[\r\n]/; + + this.file = file; + + if(json) { + var info = Zotero.JSON.unserialize(json); + } else { + var fStream = Components.classes["@mozilla.org/network/file-input-stream;1"]. + createInstance(Components.interfaces.nsIFileInputStream); + var cStream = Components.classes["@mozilla.org/intl/converter-input-stream;1"]. + createInstance(Components.interfaces.nsIConverterInputStream); + fStream.init(file, -1, -1, 0); + cStream.init(fStream, "UTF-8", 8192, + Components.interfaces.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER); + + var str = {}; + cStream.readString(MAX_INFO_LENGTH, str); + + // We assume lastUpdated is at the end to avoid running the regexp on more than necessary + var lastUpdatedIndex = str.value.indexOf('"lastUpdated"'); + if (lastUpdatedIndex == -1) { + this.logError("Invalid or missing translator metadata JSON object in " + file.leafName); + fStream.close(); + return; + } + + // Add 50 characters to clear lastUpdated timestamp and final "}" + var header = str.value.substr(0, lastUpdatedIndex + 50); + var m = infoRe.exec(header); + if (!m) { + this.logError("Invalid or missing translator metadata JSON object in " + file.leafName); + fStream.close(); + return; + } + + this.metadataString = m[0]; + + try { + var info = Zotero.JSON.unserialize(this.metadataString); + } catch(e) { + this.logError("Invalid or missing translator metadata JSON object in " + file.leafName); + fStream.close(); + return; + } + } + + var haveMetadata = true; + // make sure we have all the properties + for each(var property in ["translatorID", "translatorType", "label", "creator", "target", "minVersion", "maxVersion", "priority", "lastUpdated", "inRepository"]) { + if(info[property] === undefined) { + this.logError('Missing property "'+property+'" in translator metadata JSON object in ' + file.leafName); + haveMetadata = false; + break; + } else { + this[property] = info[property]; + } + } + if(!haveMetadata) { + fStream.close(); + return; + } + + this.configOptions = info["configOptions"] ? info["configOptions"] : {}; + this.displayOptions = info["displayOptions"] ? info["displayOptions"] : {}; + this.browserSupport = info["browserSupport"] ? info["browserSupport"] : "g"; + + if(this.translatorType & TRANSLATOR_TYPES["import"]) { + // compile import regexp to match only file extension + this.importRegexp = this.target ? new RegExp("\\."+this.target+"$", "i") : null; + } + + this.cacheCode = false; + if(this.translatorType & TRANSLATOR_TYPES["web"]) { + // compile web regexp + this.webRegexp = this.target ? new RegExp(this.target, "i") : null; + + if(!this.target) { + this.cacheCode = true; + + if(json) { + // if have JSON, also have code + this.code = code; + } else { + // for translators used on every page, cache code in memory + var strs = [str.value]; + var amountRead; + while(amountRead = cStream.readString(8192, str)) strs.push(str.value); + this.code = strs.join(""); + } + } + } + + if(!this.cacheCode) this.__defineGetter__("code", codeGetterFunction); + if(!json) cStream.close(); +} + +/** + * Log a translator-related error + * @param {String} message The error message + * @param {String} [type] The error type ("error", "warning", "exception", or "strict") + * @param {String} [line] The text of the line on which the error occurred + * @param {Integer} lineNumber + * @param {Integer} colNumber + */ +Zotero.Translator.prototype.logError = function(message, type, line, lineNumber, colNumber) { + var ios = Components.classes["@mozilla.org/network/io-service;1"]. + getService(Components.interfaces.nsIIOService); + Zotero.log(message, type ? type : "error", ios.newFileURI(this.file).spec); +} \ No newline at end of file diff --git a/chrome/content/zotero/xpcom/utilities.js b/chrome/content/zotero/xpcom/utilities.js index cb0c70a8cf..a0d1abc941 100644 --- a/chrome/content/zotero/xpcom/utilities.js +++ b/chrome/content/zotero/xpcom/utilities.js @@ -85,6 +85,7 @@ Zotero.Utilities = { */ "trim":function(/**String*/ s) { if (typeof(s) != "string") { + a() throw "trim: argument must be a string"; } @@ -271,7 +272,7 @@ Zotero.Utilities = { var pairs = matches[1].match(/([^ =]+)="([^"]+")/g); for(var j=0; j; @@ -197,10 +196,10 @@ function doExport() { originInfo += {item.distributor}; } if(item.date) { - if(Zotero.Utilities.inArray(item.itemType, ["book", "bookSection"])) { + if(["book", "bookSection"].indexOf(item.itemType) !== -1) { // Assume year is copyright date var dateType = "copyrightDate"; - } else if(Zotero.Utilities.inArray(item.itemType, ["journalArticle", "magazineArticle", "newspaperArticle"])) { + } else if(["journalArticle", "magazineArticle", "newspaperArticle"].indexOf(item.itemType) !== -1) { // Assume date is date issued var dateType = "dateIssued"; } else { @@ -542,7 +541,7 @@ function doImport() { if(!newItem.itemType) newItem.itemType = "document"; } - var isPartialItem = Zotero.Utilities.inArray(newItem.itemType, partialItemTypes); + var isPartialItem = partialItemTypes.indexOf(newItem.itemType) !== -1; // TODO: thesisType, type diff --git a/translators/RDF.js b/translators/RDF.js index a92d8ee487..43a56ff5b8 100644 --- a/translators/RDF.js +++ b/translators/RDF.js @@ -7,12 +7,11 @@ "minVersion":"1.0.0b4.r1", "maxVersion":"", "priority":100, + "configOptions":{"dataMode":"rdf/xml"}, "inRepository":true, "lastUpdated":"2009-11-12 07:20:00" } -Zotero.configure("dataMode", "rdf"); - function detectImport() { // unfortunately, Mozilla will let you create a data source from any type // of XML, so we need to make sure there are actually nodes diff --git a/translators/RIS.js b/translators/RIS.js index 65d3a4fedf..675cc604d0 100644 --- a/translators/RIS.js +++ b/translators/RIS.js @@ -8,13 +8,11 @@ "maxVersion":"", "priority":100, "inRepository":true, + "configOptions":{"dataMode":"block"}, + "displayOptions":{"exportCharset":"UTF-8", "exportNotes":true}, "lastUpdated":"2010-09-28 21:40:00" } -Zotero.configure("dataMode", "line"); -Zotero.addOption("exportNotes", true); -Zotero.addOption("exportCharset", "UTF-8"); - function detectImport() { var line; var i = 0; diff --git a/translators/ReferBibIX.js b/translators/ReferBibIX.js index 4ea0c9cc5e..a4551e64cb 100644 --- a/translators/ReferBibIX.js +++ b/translators/ReferBibIX.js @@ -7,13 +7,12 @@ "minVersion":"1.0.0b4.r5", "maxVersion":"", "priority":100, + "configOptions":{"dataMode":"line"}, + "displayOptions":{"exportCharset":"UTF-8"}, "inRepository":true, "lastUpdated":"2009-07-17 20:20:00" } -Zotero.configure("dataMode", "line"); -Zotero.addOption("exportCharset", "UTF-8"); - function detectImport() { var lineRe = /%[A-Z0-9\*\$] .+/; var line; diff --git a/translators/Unqualified Dublin Core RDF.js b/translators/Unqualified Dublin Core RDF.js index bc08371db5..9a8658107d 100644 --- a/translators/Unqualified Dublin Core RDF.js +++ b/translators/Unqualified Dublin Core RDF.js @@ -7,12 +7,11 @@ "minVersion":"1.0.0b3.r1", "maxVersion":"", "priority":100, + "configOptions":{"dataMode":"rdf/xml"}, "inRepository":true, "lastUpdated":"2006-10-02 17:00:00" } -Zotero.configure("dataMode", "rdf"); - function doExport() { var dc = "http://purl.org/dc/elements/1.1/"; Zotero.RDF.addNamespace("dc", dc); diff --git a/translators/Wikipedia Citation Templates.js b/translators/Wikipedia Citation Templates.js index 09ac447a95..d6355ae8a2 100644 --- a/translators/Wikipedia Citation Templates.js +++ b/translators/Wikipedia Citation Templates.js @@ -7,12 +7,11 @@ "minVersion":"1.0.0b4.r1", "maxVersion":"", "priority":100, + "displayOptions":{"exportCharset":"UTF-8"}, "inRepository":true, "lastUpdated":"2008-07-17 22:05:00" } -Zotero.addOption("exportCharset", "UTF-8"); - var fieldMap = { edition:"edition", publisher:"publisher", diff --git a/translators/Zotero RDF.js b/translators/Zotero RDF.js index 84ca26f1c8..bee7b23364 100644 --- a/translators/Zotero RDF.js +++ b/translators/Zotero RDF.js @@ -7,15 +7,12 @@ "minVersion":"1.0.0b4.r1", "maxVersion":"", "priority":25, + "configOptions":{"getCollections":"true", "dataMode":"rdf/xml"}, + "displayOptions":{"exportNotes":true, "exportFileData":false}, "inRepository":true, "lastUpdated":"2010-10-10 02:07:05" } -Zotero.configure("getCollections", true); -Zotero.configure("dataMode", "rdf"); -Zotero.addOption("exportNotes", true); -Zotero.addOption("exportFileData", false); - var rdf = "http://www.w3.org/1999/02/22-rdf-syntax-ns#"; var n = {