www

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

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 }