utilities_translate.js (11575B)
1 /* 2 ***** BEGIN LICENSE BLOCK ***** 3 4 Copyright © 2009 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 24 Utilities based in part on code taken from Piggy Bank 2.1.1 (BSD-licensed) 25 26 ***** END LICENSE BLOCK ***** 27 */ 28 29 /** 30 * @class All functions accessible from within Zotero.Utilities namespace inside sandboxed 31 * translators 32 * 33 * @constructor 34 * @augments Zotero.Utilities 35 * @borrows Zotero.Date.formatDate as this.formatDate 36 * @borrows Zotero.Date.strToDate as this.strToDate 37 * @borrows Zotero.Date.strToISO as this.strToISO 38 * @borrows Zotero.OpenURL.createContextObject as this.createContextObject 39 * @borrows Zotero.OpenURL.parseContextObject as this.parseContextObject 40 * @borrows Zotero.HTTP.processDocuments as this.processDocuments 41 * @borrows Zotero.HTTP.doPost as this.doPost 42 * @param {Zotero.Translate} translate 43 */ 44 Zotero.Utilities.Translate = function(translate) { 45 this._translate = translate; 46 } 47 48 var tmp = function() {}; 49 tmp.prototype = Zotero.Utilities; 50 Zotero.Utilities.Translate.prototype = new tmp(); 51 52 Zotero.Utilities.Translate.prototype.formatDate = Zotero.Date.formatDate; 53 Zotero.Utilities.Translate.prototype.strToDate = Zotero.Date.strToDate; 54 Zotero.Utilities.Translate.prototype.strToISO = Zotero.Date.strToISO; 55 Zotero.Utilities.Translate.prototype.createContextObject = Zotero.OpenURL.createContextObject; 56 Zotero.Utilities.Translate.prototype.parseContextObject = Zotero.OpenURL.parseContextObject; 57 58 /** 59 * Hack to overloads {@link Zotero.Utilities.capitalizeTitle} to allow overriding capitalizeTitles 60 * pref on a per-translate instance basis (for translator testing only) 61 */ 62 Zotero.Utilities.Translate.prototype.capitalizeTitle = function(string, force) { 63 if(force === undefined) { 64 var translate = this._translate; 65 do { 66 if(translate.capitalizeTitles !== undefined) { 67 force = translate.capitalizeTitles; 68 break; 69 } 70 } while(translate = translate._parentTranslator); 71 } 72 73 return Zotero.Utilities.capitalizeTitle(string, force); 74 } 75 76 /** 77 * Gets the current Zotero version 78 * 79 * @type String 80 */ 81 Zotero.Utilities.Translate.prototype.getVersion = function() { 82 return Zotero.version; 83 } 84 85 /** 86 * Takes an XPath query and returns the results 87 * 88 * @deprecated Use {@link Zotero.Utilities.xpath} or doc.evaluate() directly 89 * @type Node[] 90 */ 91 Zotero.Utilities.Translate.prototype.gatherElementsOnXPath = function(doc, parentNode, xpath, nsResolver) { 92 var elmts = []; 93 94 var iterator = doc.evaluate(xpath, parentNode, nsResolver, 95 (Zotero.isFx ? Components.interfaces.nsIDOMXPathResult.ANY_TYPE : XPathResult.ANY_TYPE), 96 null); 97 var elmt = iterator.iterateNext(); 98 var i = 0; 99 while (elmt) { 100 elmts[i++] = elmt; 101 elmt = iterator.iterateNext(); 102 } 103 return elmts; 104 } 105 106 /** 107 * Gets a given node as a string containing all child nodes 108 * 109 * @deprecated Use doc.evaluate and the "nodeValue" or "textContent" property 110 * @type String 111 */ 112 Zotero.Utilities.Translate.prototype.getNodeString = function(doc, contextNode, xpath, nsResolver) { 113 var elmts = this.gatherElementsOnXPath(doc, contextNode, xpath, nsResolver); 114 var returnVar = ""; 115 for(var i=0; i<elmts.length; i++) { 116 returnVar += elmts[i].nodeValue; 117 } 118 return returnVar; 119 } 120 121 /** 122 * Grabs items based on URLs 123 * 124 * @param {Document} doc DOM document object 125 * @param {Element|Element[]} inHere DOM element(s) to process 126 * @param {RegExp} [urlRe] Regexp of URLs to add to list 127 * @param {RegExp} [urlRe] Regexp of URLs to reject 128 * @return {Object} Associative array of link => textContent pairs, suitable for passing to 129 * Zotero.selectItems from within a translator 130 */ 131 Zotero.Utilities.Translate.prototype.getItemArray = function(doc, inHere, urlRe, rejectRe) { 132 var availableItems = new Object(); // Technically, associative arrays are objects 133 134 // Require link to match this 135 if(urlRe) { 136 if(urlRe.exec) { 137 var urlRegexp = urlRe; 138 } else { 139 var urlRegexp = new RegExp(); 140 urlRegexp.compile(urlRe, "i"); 141 } 142 } 143 // Do not allow text to match this 144 if(rejectRe) { 145 if(rejectRe.exec) { 146 var rejectRegexp = rejectRe; 147 } else { 148 var rejectRegexp = new RegExp(); 149 rejectRegexp.compile(rejectRe, "i"); 150 } 151 } 152 153 if(!inHere.length) { 154 inHere = new Array(inHere); 155 } 156 157 for(var j=0; j<inHere.length; j++) { 158 var links = inHere[j].getElementsByTagName("a"); 159 for(var i=0; i<links.length; i++) { 160 if(!urlRe || urlRegexp.test(links[i].href)) { 161 var text = "textContent" in links[i] ? links[i].textContent : links[i].innerText; 162 if(text) { 163 text = this.trimInternal(text); 164 if(!rejectRe || !rejectRegexp.test(text)) { 165 if(availableItems[links[i].href]) { 166 if(text != availableItems[links[i].href]) { 167 availableItems[links[i].href] += " "+text; 168 } 169 } else { 170 availableItems[links[i].href] = text; 171 } 172 } 173 } 174 } 175 } 176 } 177 178 return availableItems; 179 } 180 181 182 /** 183 * Load a single document in a hidden browser 184 * 185 * @deprecated Use processDocuments with a single URL 186 * @see Zotero.Utilities.Translate#processDocuments 187 */ 188 Zotero.Utilities.Translate.prototype.loadDocument = function(url, succeeded, failed) { 189 Zotero.debug("Zotero.Utilities.loadDocument is deprecated; please use processDocuments in new code"); 190 this.processDocuments([url], succeeded, null, failed); 191 } 192 193 /** 194 * Already documented in Zotero.HTTP, except this optionally takes noCompleteOnError, which prevents 195 * the translation process from being cancelled automatically on error, as it is normally. The promise 196 * is still rejected on error for handling by the calling function. 197 * @ignore 198 */ 199 Zotero.Utilities.Translate.prototype.processDocuments = async function (urls, processor, noCompleteOnError) { 200 // Handle old signature: urls, processor, onDone, onError 201 if (arguments.length > 3 || typeof arguments[2] == 'function') { 202 Zotero.debug("ZU.processDocuments() now takes only 3 arguments -- update your code"); 203 var onDone = arguments[2]; 204 var onError = arguments[3]; 205 noCompleteOnError = false; 206 } 207 208 var translate = this._translate; 209 210 if (typeof urls == "string") { 211 urls = [translate.resolveURL(urls)]; 212 } else { 213 for(var i in urls) { 214 urls[i] = translate.resolveURL(urls[i]); 215 } 216 } 217 218 var processDoc = function (doc) { 219 if (Zotero.isFx) { 220 let newLoc = doc.location; 221 let url = Services.io.newURI(newLoc.href, null, null); 222 return processor( 223 // Rewrap document for the sandbox 224 translate._sandboxManager.wrap( 225 Zotero.Translate.DOMWrapper.unwrap(doc), 226 null, 227 // Duplicate overrides from Zotero.HTTP.wrapDocument() 228 { 229 documentURI: newLoc.spec, 230 URL: newLoc.spec, 231 location: new Zotero.HTTP.Location(url), 232 defaultView: new Zotero.HTTP.Window(url) 233 } 234 ), 235 newLoc.href 236 ); 237 } 238 239 return processor(doc, doc.location.href); 240 }; 241 242 var funcs = []; 243 // If current URL passed, use loaded document instead of reloading 244 for (var i = 0; i < urls.length; i++) { 245 if(translate.document && translate.document.location 246 && translate.document.location.toString() === urls[i]) { 247 Zotero.debug("Translate: Attempted to load the current document using processDocuments; using loaded document instead"); 248 funcs.push(() => processDoc(this._translate.document, urls[i])); 249 urls.splice(i, 1); 250 i--; 251 } 252 } 253 254 translate.incrementAsyncProcesses("Zotero.Utilities.Translate#processDocuments"); 255 256 if (urls.length) { 257 funcs.push( 258 () => Zotero.HTTP.processDocuments( 259 urls, 260 function (doc) { 261 if (!processor) return; 262 return processDoc(doc); 263 }, 264 translate.cookieSandbox 265 ) 266 ); 267 } 268 269 var f; 270 while (f = funcs.shift()) { 271 try { 272 let maybePromise = f(); 273 // The processor may or may not return a promise 274 if (maybePromise) { 275 await maybePromise; 276 } 277 } 278 catch (e) { 279 if (onError) { 280 try { 281 onError(e); 282 } 283 catch (e) { 284 translate.complete(false, e); 285 } 286 } 287 // Unless instructed otherwise, end the translation on error 288 else if (!noCompleteOnError) { 289 translate.complete(false, e); 290 } 291 throw e; 292 } 293 } 294 295 // Deprecated 296 if (onDone) { 297 onDone(); 298 } 299 300 translate.decrementAsyncProcesses("Zotero.Utilities.Translate#processDocuments"); 301 } 302 303 /** 304 * Send an HTTP GET request via XMLHTTPRequest 305 * 306 * @param {String|String[]} urls URL(s) to request 307 * @param {Function} processor Callback to be executed for each document loaded 308 * @param {Function} done Callback to be executed after all documents have been loaded 309 * @param {String} responseCharset Character set to force on the response 310 * @param {Object} requestHeaders HTTP headers to include with request 311 * @return {Boolean} True if the request was sent, or false if the browser is offline 312 */ 313 Zotero.Utilities.Translate.prototype.doGet = function(urls, processor, done, responseCharset, requestHeaders) { 314 var callAgain = false, 315 me = this, 316 translate = this._translate; 317 318 if(typeof(urls) == "string") { 319 var url = urls; 320 } else { 321 if(urls.length > 1) callAgain = true; 322 var url = urls.shift(); 323 } 324 325 url = translate.resolveURL(url); 326 327 translate.incrementAsyncProcesses("Zotero.Utilities.Translate#doGet"); 328 var xmlhttp = Zotero.HTTP.doGet(url, function(xmlhttp) { 329 try { 330 if(processor) { 331 processor(xmlhttp.responseText, xmlhttp, url); 332 } 333 334 if(callAgain) { 335 me.doGet(urls, processor, done, responseCharset); 336 } else { 337 if(done) { 338 done(); 339 } 340 } 341 translate.decrementAsyncProcesses("Zotero.Utilities.Translate#doGet"); 342 } catch(e) { 343 translate.complete(false, e); 344 } 345 }, responseCharset, this._translate.cookieSandbox, requestHeaders); 346 } 347 348 /** 349 * Already documented in Zotero.HTTP 350 * @ignore 351 */ 352 Zotero.Utilities.Translate.prototype.doPost = function(url, body, onDone, headers, responseCharset) { 353 var translate = this._translate; 354 url = translate.resolveURL(url); 355 356 translate.incrementAsyncProcesses("Zotero.Utilities.Translate#doPost"); 357 var xmlhttp = Zotero.HTTP.doPost(url, body, function(xmlhttp) { 358 try { 359 onDone(xmlhttp.responseText, xmlhttp); 360 translate.decrementAsyncProcesses("Zotero.Utilities.Translate#doPost"); 361 } catch(e) { 362 translate.complete(false, e); 363 } 364 }, headers, responseCharset, translate.cookieSandbox ? translate.cookieSandbox : undefined); 365 } 366 367 Zotero.Utilities.Translate.prototype.urlToProxy = function(url) { 368 var proxy = this._translate._proxy; 369 if (proxy) return proxy.toProxy(url); 370 return url; 371 }; 372 373 Zotero.Utilities.Translate.prototype.urlToProper = function(url) { 374 var proxy = this._translate._proxy; 375 if (proxy) return proxy.toProper(url); 376 return url; 377 }; 378 379 Zotero.Utilities.Translate.prototype.__exposedProps__ = {"HTTP":"r"}; 380 for(var j in Zotero.Utilities.Translate.prototype) { 381 if(typeof Zotero.Utilities.Translate.prototype[j] === "function" && j[0] !== "_" && j != "Translate") { 382 Zotero.Utilities.Translate.prototype.__exposedProps__[j] = "r"; 383 } 384 }