www

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | Submodules | README | LICENSE

translator.js (8070B)


      1 /*
      2     ***** BEGIN LICENSE BLOCK *****
      3     
      4     Copyright © 2013 Center for History and New Media
      5                      George Mason University, Fairfax, Virginia, USA
      6                      http://zotero.org
      7     
      8     This file is part of Zotero.
      9     
     10     Zotero is free software: you can redistribute it and/or modify
     11     it under the terms of the GNU Affero General Public License as published by
     12     the Free Software Foundation, either version 3 of the License, or
     13     (at your option) any later version.
     14     
     15     Zotero is distributed in the hope that it will be useful,
     16     but WITHOUT ANY WARRANTY; without even the implied warranty of
     17     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     18     GNU Affero General Public License for more details.
     19     
     20     You should have received a copy of the GNU Affero General Public License
     21     along with Zotero.  If not, see <http://www.gnu.org/licenses/>.
     22     
     23     ***** END LICENSE BLOCK *****
     24 */
     25 
     26 // Enumeration of types of translators
     27 var TRANSLATOR_TYPES = {"import":1, "export":2, "web":4, "search":8};
     28 
     29 // Properties required for every translator
     30 var TRANSLATOR_REQUIRED_PROPERTIES = ["translatorID", "translatorType", "label", "creator",
     31                                       "target", "priority", "lastUpdated"];
     32 // Properties that are preserved if present
     33 var TRANSLATOR_OPTIONAL_PROPERTIES = ["targetAll", "browserSupport", "minVersion", "maxVersion",
     34                                       "inRepository", "configOptions", "displayOptions",
     35                                       "hiddenPrefs", "itemType"];
     36 // Properties that are passed from background to inject page in connector
     37 var TRANSLATOR_PASSING_PROPERTIES = TRANSLATOR_REQUIRED_PROPERTIES.
     38                                     concat(["targetAll", "browserSupport", "code", "runMode", "itemType", "inRepository"]);
     39 
     40 /**
     41  * @class Represents an individual translator
     42  * @constructor
     43  * @property {String} translatorID Unique GUID of the translator
     44  * @property {Integer} translatorType Type of the translator (use bitwise & with TRANSLATOR_TYPES to read)
     45  * @property {String} label Human-readable name of the translator
     46  * @property {String} creator Author(s) of the translator
     47  * @property {String} target Location that the translator processes
     48  * @property {String} minVersion Minimum Zotero version
     49  * @property {String} maxVersion Minimum Zotero version
     50  * @property {Integer} priority Lower-priority translators will be selected first
     51  * @property {String} browserSupport String indicating browser supported by the translator
     52  *     g = Gecko (Firefox)
     53  *     c = Google Chrome (WebKit & V8)
     54  *     s = Safari (WebKit & Nitro/Squirrelfish Extreme)
     55  *     i = Internet Explorer
     56  *     b = Bookmarklet
     57  *     v = Server
     58  * @property {Object} configOptions Configuration options for import/export
     59  * @property {Object} displayOptions Display options for export
     60  * @property {Object} hiddenPrefs Hidden preferences configurable through about:config
     61  * @property {Boolean} inRepository Whether the translator may be found in the repository
     62  * @property {String} lastUpdated SQL-style date and time of translator's last update
     63  * @property {Object} metadata - Metadata block as object
     64  * @property {String} code The executable JavaScript for the translator
     65  * @property {Boolean} cacheCode Whether to cache code for this session (non-connector only)
     66  * @property {String} [path] File path corresponding to this translator (non-connector only)
     67  * @property {String} [fileName] File name corresponding to this translator (non-connector only)
     68  */
     69 Zotero.Translator = function(info) {
     70 	this.init(info);
     71 }
     72 
     73 /**
     74  * Initializes a translator from a set of info, clearing code if it is set
     75  */
     76 Zotero.Translator.prototype.init = function(info) {
     77 	// make sure we have all the properties
     78 	for (let property of TRANSLATOR_REQUIRED_PROPERTIES) {
     79 		if (info[property] === undefined) {
     80 			this.logError(new Error('Missing property "'+property+'" in translator metadata JSON object in ' + info.label));
     81 			break;
     82 		} else {
     83 			this[property] = info[property];
     84 		}
     85 	}
     86 	for (let property of TRANSLATOR_OPTIONAL_PROPERTIES) {
     87 		if(info[property] !== undefined) {
     88 			this[property] = info[property];
     89 		}
     90 	}
     91 	
     92 	this.browserSupport = info["browserSupport"] ? info["browserSupport"] : "g";
     93 	
     94 	var supported = !Zotero.isBookmarklet || this.browserSupport.includes("b") ||
     95 			/(?:^|; ?)bookmarklet-debug-mode=1(?:$|; ?)/.test(document.cookie);
     96 
     97 	if (supported) {
     98 		this.runMode = Zotero.Translator.RUN_MODE_IN_BROWSER;
     99 	} else {
    100 		this.runMode = Zotero.Translator.RUN_MODE_ZOTERO_STANDALONE;
    101 	}
    102 	
    103 	if (this.translatorType & TRANSLATOR_TYPES["import"]) {
    104 		// compile import regexp to match only file extension
    105 		this.importRegexp = this.target ? new RegExp("\\."+this.target+"$", "i") : null;
    106 	} else if (this.hasOwnProperty("importRegexp")) {
    107 		delete this.importRegexp;
    108 	}
    109 	
    110 	this.cacheCode = Zotero.isConnector;
    111 	if (this.translatorType & TRANSLATOR_TYPES["web"]) {
    112 		// compile web regexp
    113 		this.cacheCode |= !this.target;
    114 		this.webRegexp = {
    115 			root: this.target ? new RegExp(this.target, "i") : null,
    116 			all: this.targetAll ? new RegExp(this.targetAll, "i") : null
    117 		};
    118 	} else if (this.hasOwnProperty("webRegexp")) {
    119 		delete this.webRegexp;
    120 	}
    121 	
    122 	if (info.path) {
    123 		this.path = info.path;
    124 		this.fileName = OS.Path.basename(info.path);
    125 	}
    126 	if (info.code && this.cacheCode) {
    127 		this.code = Zotero.Translator.replaceDeprecatedStatements(info.code);
    128 	} else if (this.hasOwnProperty("code")) {
    129 		delete this.code;
    130 	}
    131 	// Save a copy of the metadata block
    132 	delete info.path;
    133 	delete info.code;
    134 	this.metadata = info;
    135 
    136 }
    137 
    138 /**
    139  * Load code for a translator
    140  */
    141 Zotero.Translator.prototype.getCode = Zotero.Promise.method(function () {
    142 	if (this.code) return this.code;
    143 
    144 	if (Zotero.isConnector) {
    145 		return Zotero.Repo.getTranslatorCode(this.translatorID)
    146 		.then(function (args) {
    147 			var code = args[0];
    148 			var source = args[1];
    149 			if(!code) {
    150 				throw new Error("Code for " + this.label + " could not be retrieved");
    151 			}
    152 			// Cache any translators for session, since retrieving via
    153 			// HTTP may be expensive
    154 			this.code = code;
    155 			this.codeSource = source;
    156 			return code;
    157 		}.bind(this));
    158 	} else {
    159 		var promise = Zotero.File.getContentsAsync(this.path);
    160 		if(this.cacheCode) {
    161 			// Cache target-less web translators for session, since we
    162 			// will use them a lot
    163 			return promise.then(function(code) {
    164 				this.code = code;
    165 				return code;
    166 			}.bind(this));
    167 		}
    168 		return promise;
    169 	}
    170 });
    171 
    172 /**
    173  * Get metadata block for a translator
    174  */
    175 Zotero.Translator.prototype.serialize = function(properties) {
    176 	var info = {};
    177 	for(var i in properties) {
    178 		var property = properties[i];
    179 		info[property] = this[property];
    180 	}
    181 	return info;
    182 }
    183 
    184 /**
    185  * Log a translator-related error
    186  * @param {String} message The error message
    187  * @param {String} [type] The error type ("error", "warning", "exception", or "strict")
    188  * @param {String} [line] The text of the line on which the error occurred
    189  * @param {Integer} lineNumber
    190  * @param {Integer} colNumber
    191  */
    192 Zotero.Translator.prototype.logError = function(message, type, line, lineNumber, colNumber) {
    193 	if (Zotero.isFx && this.path) {
    194 		Components.utils.import("resource://gre/modules/FileUtils.jsm");
    195 		var file = new FileUtils.File(this.path);
    196 		var ios = Components.classes["@mozilla.org/network/io-service;1"].
    197 			getService(Components.interfaces.nsIIOService);
    198 		Zotero.log(message, type ? type : "error", ios.newFileURI(file).spec);
    199 		Zotero.debug(message, 1);
    200 	} else {
    201 		Zotero.logError(message);
    202 	}
    203 }
    204 
    205 /**
    206  * Replace deprecated ES5 statements
    207  */
    208 Zotero.Translator.replaceDeprecatedStatements = function(code) {
    209 	const foreach = /^(\s*)for each\s*\((var )?([^ ]+) in (.*?)\)(\s*){/gm;
    210 	code = code.replace(foreach, "$1var $3_zForEachSubject = $4; "+
    211 		"for(var $3_zForEachIndex in $3_zForEachSubject)$5{ "+
    212 		"$2$3 = $3_zForEachSubject[$3_zForEachIndex];");
    213 	return code;
    214 }
    215 
    216 Zotero.Translator.RUN_MODE_IN_BROWSER = 1;
    217 Zotero.Translator.RUN_MODE_ZOTERO_STANDALONE = 2;
    218 Zotero.Translator.RUN_MODE_ZOTERO_SERVER = 4;