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;