openurl.js (17175B)
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 ***** END LICENSE BLOCK ***** 24 */ 25 26 Zotero.OpenURL = new function() { 27 this.resolve = resolve; 28 this.discoverResolvers = discoverResolvers; 29 this.createContextObject = createContextObject; 30 this.parseContextObject = parseContextObject; 31 32 /* 33 * Returns a URL to look up an item in the OpenURL resolver 34 */ 35 function resolve(itemObject) { 36 var co = createContextObject(itemObject, Zotero.Prefs.get("openURL.version")); 37 if(co) { 38 var base = Zotero.Prefs.get("openURL.resolver"); 39 // Add & if there's already a ? 40 var splice = base.indexOf("?") == -1 ? "?" : "&"; 41 return base + splice + co; 42 } 43 return false; 44 } 45 46 /* 47 * Queries OCLC's OpenURL resolver registry and returns an address and version 48 */ 49 function discoverResolvers() { 50 var req = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance(); 51 req.open("GET", "http://worldcatlibraries.org/registry/lookup?IP=requestor", false); 52 req.send(null); 53 54 if(!req.responseXML) { 55 throw "Could not access resolver registry"; 56 } 57 58 var resolverArray = new Array(); 59 var resolvers = req.responseXML.getElementsByTagName("resolver"); 60 for(var i=0; i<resolvers.length; i++) { 61 var resolver = resolvers[i]; 62 63 var name = resolver.parentNode.getElementsByTagName("institutionName"); 64 if(!name.length) { 65 continue; 66 } 67 name = name[0].textContent; 68 69 var url = resolver.getElementsByTagName("baseURL"); 70 if(!url.length) { 71 continue; 72 } 73 url = url[0].textContent; 74 75 if(resolver.getElementsByTagName("Z39.88-2004").length > 0) { 76 var version = "1.0"; 77 } else if(resolver.getElementsByTagName("OpenURL_0.1").length > 0) { 78 var version = "0.1"; 79 } else { 80 continue; 81 } 82 83 resolverArray.push({name:name, url:url, version:version}); 84 } 85 86 return resolverArray; 87 } 88 89 /* 90 * Generates an OpenURL ContextObject from an item 91 */ 92 function createContextObject(item, version, asObj) { 93 var entries = (asObj ? {} : []); 94 95 function _mapTag(data, tag, dontAddPrefix) { 96 if(!data) return; 97 98 if(version === "1.0" && !dontAddPrefix) tag = "rft."+tag; 99 100 if(asObj) { 101 if(!entries[tag]) entries[tag] = []; 102 entries[tag].push(data); 103 } else { 104 entries.push(tag+"="+encodeURIComponent(data)); 105 } 106 } 107 108 if (item.toJSON) { 109 item = item.toJSON(); 110 } 111 112 // find pmid 113 const pmidRe = /(?:\n|^)PMID:\s*(\d+)/g; 114 var pmid = pmidRe.exec(item.extra); 115 if(pmid) pmid = pmid[1]; 116 117 // encode ctx_ver (if available) and encode identifiers 118 if(version == "0.1") { 119 _mapTag("Zotero:2", "sid", true); 120 if(item.DOI) _mapTag("doi:"+item.DOI, "id", true); 121 if(item.ISBN) _mapTag(item.ISBN, "isbn", true); 122 if(pmid) _mapTag("pmid:"+pmid, "id", true); 123 } else { 124 _mapTag("Z39.88-2004", "url_ver", true); 125 _mapTag("Z39.88-2004", "ctx_ver", true); 126 _mapTag("info:sid/zotero.org:2", "rfr_id", true); 127 if(item.DOI) _mapTag("info:doi/"+item.DOI, "rft_id", true); 128 if(item.ISBN) _mapTag("urn:isbn:"+item.ISBN, "rft_id", true); 129 if(pmid) _mapTag("info:pmid/"+pmid, "rft_id", true); 130 } 131 132 // encode genre and item-specific data 133 if(item.itemType == "journalArticle") { 134 if(version === "1.0") { 135 _mapTag("info:ofi/fmt:kev:mtx:journal", "rft_val_fmt", true); 136 } 137 _mapTag("article", "genre"); 138 139 _mapTag(item.title, "atitle"); 140 _mapTag(item.publicationTitle, (version == "0.1" ? "title" : "jtitle")); 141 _mapTag(item.journalAbbreviation, "stitle"); 142 _mapTag(item.volume, "volume"); 143 _mapTag(item.issue, "issue"); 144 } else if(item.itemType == "book" || item.itemType == "bookSection" || item.itemType == "conferencePaper" || item.itemType == "report") { 145 if(version === "1.0") { 146 _mapTag("info:ofi/fmt:kev:mtx:book", "rft_val_fmt", true); 147 } 148 149 if(item.itemType == "book") { 150 _mapTag("book", "genre"); 151 _mapTag(item.title, (version == "0.1" ? "title" : "btitle")); 152 } else if (item.itemType == "conferencePaper") { 153 _mapTag("proceeding", "genre"); 154 _mapTag(item.title, "atitle"); 155 _mapTag(item.proceedingsTitle, (version == "0.1" ? "title" : "btitle")); 156 } else if (item.itemType == "report") { 157 _mapTag("report", "genre"); 158 _mapTag(item.seriesTitle, "series"); 159 _mapTag(item.title, (version == "0.1" ? "title" : "btitle")); 160 } else { 161 _mapTag("bookitem", "genre"); 162 _mapTag(item.title, "atitle"); 163 _mapTag(item.publicationTitle, (version == "0.1" ? "title" : "btitle")); 164 } 165 166 _mapTag(item.place, "place"); 167 _mapTag(item.publisher, "publisher"); 168 _mapTag(item.edition, "edition"); 169 _mapTag(item.series, "series"); 170 } else if(item.itemType == "thesis" && version == "1.0") { 171 _mapTag("info:ofi/fmt:kev:mtx:dissertation", "rft_val_fmt", true); 172 173 _mapTag(item.title, "title"); 174 _mapTag(item.publisher, "inst"); 175 _mapTag(item.type, "degree"); 176 } else if(item.itemType == "patent" && version == "1.0") { 177 _mapTag("info:ofi/fmt:kev:mtx:patent", "rft_val_fmt", true); 178 179 _mapTag(item.title, "title"); 180 _mapTag(item.assignee, "assignee"); 181 _mapTag(item.patentNumber, "number"); 182 183 if(item.issueDate) { 184 _mapTag(Zotero.Date.strToISO(item.issueDate), "date"); 185 } 186 } else { 187 //we map as much as possible to DC for all other types. This will export some info 188 //and work very nicely on roundtrip. All of these fields legal for mtx:dc according to 189 //http://alcme.oclc.org/openurl/servlet/OAIHandler/extension?verb=GetMetadata&metadataPrefix=mtx&identifier=info:ofi/fmt:kev:mtx:dc 190 _mapTag("info:ofi/fmt:kev:mtx:dc", "rft_val_fmt", true); 191 //lacking something better we use Zotero item types here; no clear alternative and this works for roundtrip 192 _mapTag(item.itemType, "type"); 193 _mapTag(item.title, "title"); 194 _mapTag(item.publicationTitle, "source"); 195 _mapTag(item.rights, "rights"); 196 _mapTag(item.publisher, "publisher"); 197 _mapTag(item.abstractNote, "description"); 198 if(item.DOI){ 199 _mapTag("urn:doi:" + item.DOI, "identifier"); 200 } 201 else if(item.url){ 202 _mapTag(item.url, "identifier"); 203 } 204 } 205 206 if(item.creators && item.creators.length) { 207 // encode first author as first and last 208 let firstCreator = Zotero.Items.getFirstCreatorFromJSON(item); 209 if(item.itemType == "patent") { 210 _mapTag(firstCreator.firstName, "invfirst"); 211 _mapTag(firstCreator.lastName, "invlast"); 212 } else { 213 if(firstCreator.isInstitution) { 214 _mapTag(firstCreator.lastName, "aucorp"); 215 } else { 216 _mapTag(firstCreator.firstName, "aufirst"); 217 _mapTag(firstCreator.lastName, "aulast"); 218 } 219 } 220 221 // encode subsequent creators as au 222 for(var i=0; i<item.creators.length; i++) { 223 _mapTag((item.creators[i].firstName ? item.creators[i].firstName+" " : "")+ 224 item.creators[i].lastName, (item.itemType == "patent" ? "inventor" : "au")); 225 } 226 } 227 228 if(item.date) { 229 _mapTag(Zotero.Date.strToISO(item.date), (item.itemType == "patent" ? "appldate" : "date")); 230 } 231 if(item.pages) { 232 _mapTag(item.pages, "pages"); 233 var pages = item.pages.split(/[-–]/); 234 if(pages.length > 1) { 235 _mapTag(pages[0], "spage"); 236 if(pages.length >= 2) _mapTag(pages[1], "epage"); 237 } 238 } 239 _mapTag(item.numPages, "tpages"); 240 _mapTag(item.ISBN, "isbn"); 241 _mapTag(item.ISSN, "issn"); 242 _mapTag(item.language, "language"); 243 if(asObj) return entries; 244 return entries.join("&"); 245 } 246 247 function _cloneIfNecessary(obj1, obj2) { 248 if (Zotero.isFx && !Zotero.isBookmarklet) { 249 return Components.utils.cloneInto(obj1, obj2); 250 } 251 return obj1; 252 } 253 254 /* 255 * Generates an item in the format returned by item.fromArray() given an 256 * OpenURL version 1.0 contextObject 257 * 258 * accepts an item array to fill, or creates and returns a new item array 259 */ 260 function parseContextObject(co, item) { 261 if(!item) { 262 var item = new Array(); 263 item.creators = new Array(); 264 } 265 266 var coParts = co.split("&"); 267 268 // get type 269 for(var i=0; i<coParts.length; i++) { 270 if(coParts[i].substr(0, 12) == "rft_val_fmt=") { 271 var format = decodeURIComponent(coParts[i].substr(12)); 272 if(format == "info:ofi/fmt:kev:mtx:journal") { 273 item.itemType = "journalArticle"; 274 break; 275 } else if(format == "info:ofi/fmt:kev:mtx:book") { 276 if(coParts.indexOf("rft.genre=bookitem") !== -1) { 277 item.itemType = "bookSection"; 278 } else if(coParts.indexOf("rft.genre=conference") !== -1 || coParts.indexOf("rft.genre=proceeding") !== -1) { 279 item.itemType = "conferencePaper"; 280 } else if(coParts.indexOf("rft.genre=report") !== -1) { 281 item.itemType = "report"; 282 } else if(coParts.indexOf("rft.genre=document") !== -1) { 283 item.itemType = "document"; 284 } else { 285 item.itemType = "book"; 286 } 287 break; 288 } else if(format == "info:ofi/fmt:kev:mtx:dissertation") { 289 item.itemType = "thesis"; 290 break; 291 } else if(format == "info:ofi/fmt:kev:mtx:patent") { 292 item.itemType = "patent"; 293 break; 294 } else if(format == "info:ofi/fmt:kev:mtx:dc") { 295 item.itemType = "webpage"; 296 break; 297 } 298 } 299 } 300 if(!item.itemType) { 301 return false; 302 } 303 304 var pagesKey = ""; 305 306 // keep track of "aucorp," "aufirst," "aulast" 307 var complexAu = new Array(); 308 309 for(var i=0; i<coParts.length; i++) { 310 var keyVal = coParts[i].split("="); 311 var key = keyVal[0]; 312 var value = decodeURIComponent(keyVal[1].replace(/\+|%2[bB]/g, " ")); 313 if(!value) { 314 continue; 315 } 316 317 if(key == "rft_id") { 318 var firstEight = value.substr(0, 8).toLowerCase(); 319 if(firstEight == "info:doi") { 320 item.DOI = value.substr(9); 321 } else if(firstEight == "urn:isbn") { 322 item.ISBN = value.substr(9); 323 } else if(value.match(/^https?:\/\//)) { 324 item.url = value; 325 item.accessDate = ""; 326 } 327 } else if(key == "rft.btitle") { 328 if(item.itemType == "book" || item.itemType == "report") { 329 item.title = value; 330 } else if(item.itemType == "bookSection" || item.itemType == "conferencePaper") { 331 item.publicationTitle = value; 332 } 333 } else if(key == "rft.atitle" 334 && ["journalArticle", "bookSection", "conferencePaper"].indexOf(item.itemType) !== -1) { 335 item.title = value; 336 } else if(key == "rft.jtitle" && item.itemType == "journalArticle") { 337 item.publicationTitle = value; 338 } else if(key == "rft.stitle" && item.itemType == "journalArticle") { 339 item.journalAbbreviation = value; 340 } else if(key == "rft.title") { 341 if(["journalArticle", "bookSection", "conferencePaper"].indexOf(item.itemType) !== -1) { 342 item.publicationTitle = value; 343 } else { 344 item.title = value; 345 } 346 } else if(key == "rft.date") { 347 if(item.itemType == "patent") { 348 item.issueDate = value; 349 } else { 350 item.date = value; 351 } 352 } else if(key == "rft.volume") { 353 item.volume = value; 354 } else if(key == "rft.issue") { 355 item.issue = value; 356 } else if(key == "rft.pages") { 357 pagesKey = key; 358 item.pages = value; 359 } else if(key == "rft.spage") { 360 if(pagesKey != "rft.pages") { 361 // make pages look like start-end 362 if(pagesKey == "rft.epage") { 363 if(value != item.pages) { 364 item.pages = value+"-"+item.pages; 365 } 366 } else { 367 item.pages = value; 368 } 369 pagesKey = key; 370 } 371 } else if(key == "rft.epage") { 372 if(pagesKey != "rft.pages") { 373 // make pages look like start-end 374 if(pagesKey == "rft.spage") { 375 if(value != item.pages) { 376 item.pages = item.pages+"-"+value; 377 } 378 } else { 379 item.pages = value; 380 } 381 pagesKey = key; 382 } 383 } else if(key == "rft.issn" || (key == "rft.eissn" && !item.ISSN)) { 384 item.ISSN = value; 385 } else if(key == "rft.aulast" || key == "rft.invlast") { 386 var lastCreator = complexAu[complexAu.length-1]; 387 if(complexAu.length && !lastCreator.lastName && !lastCreator.institutional) { 388 lastCreator.lastName = value; 389 } else { 390 complexAu.push(_cloneIfNecessary({lastName:value, creatorType:(key == "rft.aulast" ? "author" : "inventor"), offset:item.creators.length}, item)); 391 } 392 } else if(key == "rft.aufirst" || key == "rft.invfirst") { 393 var lastCreator = complexAu[complexAu.length-1]; 394 if(complexAu.length && !lastCreator.firstName && !lastCreator.institutional) { 395 lastCreator.firstName = value; 396 } else { 397 complexAu.push(_cloneIfNecessary({firstName:value, creatorType:(key == "rft.aufirst" ? "author" : "inventor"), offset:item.creators.length}, item)); 398 } 399 } else if(key == "rft.au" || key == "rft.creator" || key == "rft.contributor" || key == "rft.inventor") { 400 if(key == "rft.contributor") { 401 var type = "contributor"; 402 } else if(key == "rft.inventor") { 403 var type = "inventor"; 404 } else { 405 var type = "author"; 406 } 407 408 item.creators.push(_cloneIfNecessary(Zotero.Utilities.cleanAuthor(value, type, value.indexOf(",") !== -1), item)); 409 } else if(key == "rft.aucorp") { 410 complexAu.push(_cloneIfNecessary({lastName:value, isInstitution:true}, item)); 411 } else if(key == "rft.isbn" && !item.ISBN) { 412 item.ISBN = value; 413 } else if(key == "rft.pub" || key == "rft.publisher") { 414 item.publisher = value; 415 } else if(key == "rft.place") { 416 item.place = value; 417 } else if(key == "rft.tpages") { 418 item.numPages = value; 419 } else if(key == "rft.edition") { 420 item.edition = value; 421 } else if(key == "rft.series") { 422 if(item.itemType == "report") { 423 item.seriesTitle = value; 424 } else { 425 item.series = value; 426 } 427 } else if(item.itemType == "thesis") { 428 if(key == "rft.inst") { 429 item.publisher = value; 430 } else if(key == "rft.degree") { 431 item.type = value; 432 } 433 } else if(item.itemType == "patent") { 434 if(key == "rft.assignee") { 435 item.assignee = value; 436 } else if(key == "rft.number") { 437 item.patentNumber = value; 438 } else if(key == "rft.appldate") { 439 item.date = value; 440 } 441 } else { 442 // The following keys are technically only valid in Dublin Core 443 // (i.e., format == "info:ofi/fmt:kev:mtx:dc") but in practice 444 // 'format' is not always set 445 if(key == "rft.identifier") { 446 if(value.length > 8) { // we could check length separately for 447 // each type, but all of these identifiers 448 // must be > 8 characters 449 if(value.substr(0, 5) == "ISBN ") { 450 item.ISBN = value.substr(5); 451 } else if(value.substr(0, 5) == "ISSN ") { 452 item.ISSN = value.substr(5); 453 } else if(value.substr(0, 8) == "urn:doi:") { 454 item.DOI = value.substr(4); 455 } else if(value.substr(0, 7) == "http://" || value.substr(0, 8) == "https://") { 456 item.url = value; 457 } 458 } 459 } else if(key == "rft.description") { 460 item.abstractNote = value; 461 } else if(key == "rft.rights") { 462 item.rights = value; 463 } else if(key == "rft.language") { 464 item.language = value; 465 } else if(key == "rft.subject") { 466 item.tags.push(value); 467 } else if(key == "rft.type") { 468 if(Zotero.Utilities.itemTypeExists(value)) item.itemType = value; 469 } else if(key == "rft.source") { 470 item.publicationTitle = value; 471 } 472 } 473 } 474 475 // To maintain author ordering when complex and simple authors are combined, 476 // we remember where they were and the correct offsets 477 var inserted = 0; 478 479 // combine two lists of authors, eliminating duplicates 480 for(var i=0; i<complexAu.length; i++) { 481 var pushMe = true; 482 var offset = complexAu[i].offset; 483 delete complexAu[i].offset; 484 for (var j = 0; j < item.creators.length; j++) { 485 // if there's a plain author that is close to this author (the 486 // same last name, and the same first name up to a point), keep 487 // the plain author, since it might have a middle initial 488 if (item.creators[j].lastName == complexAu[i].lastName && 489 item.creators[j].firstName && 490 ((item.creators[j].firstName == "" && complexAu[i].firstName == "") || 491 (item.creators[j].firstName.length >= complexAu[i].firstName.length && 492 item.creators[j].firstName.substr(0, complexAu[i].firstName.length) == complexAu[i].firstName))) { 493 pushMe = false; 494 break; 495 } 496 } 497 // Splice in the complex creator at the correct location, 498 // accounting for previous insertions 499 if(pushMe) { 500 item.creators.splice(offset + inserted, 0, complexAu[i]); 501 inserted++; 502 } 503 } 504 505 return item; 506 } 507 }