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 = {