Permalink
Join GitHub today
GitHub is home to over 31 million developers working together to host and review code, manage projects, and build software together.
Sign up
translators/Clinical Key.js
Find file
Copy path
Fetching contributors…
Cannot retrieve contributors at this time
{ | |
"translatorID": "a55463ba-e403-415b-80d4-284d5f9b4b15", | |
"label": "Clinical Key", | |
"creator": "Jaret M. Karnuta, Mike Davidson", | |
"target": "^https?://(www\\.|www-)clinicalkey(\\.|-)com", | |
"minVersion": "3.0", | |
"maxVersion": "", | |
"priority": 100, | |
"inRepository": true, | |
"translatorType": 4, | |
"browserSupport": "gcsibv", | |
"lastUpdated": "2017-01-30 08:08:52" | |
} | |
/* | |
***** BEGIN LICENSE BLOCK ***** | |
Copyright © 2017 Jaret M. Karnuta & Mike Davidson | |
This file is part of Zotero. | |
Zotero is free software: you can redistribute it and/or modify | |
it under the terms of the GNU Affero General Public License as published by | |
the Free Software Foundation, either version 3 of the License, or | |
(at your option) any later version. | |
Zotero is distributed in the hope that it will be useful, | |
but WITHOUT ANY WARRANTY; without even the implied warranty of | |
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
GNU Affero General Public License for more details. | |
You should have received a copy of the GNU Affero General Public License | |
along with Zotero. If not, see <http://www.gnu.org/licenses/>. | |
***** END LICENSE BLOCK ***** | |
*/ | |
/* | |
This translator is designed specifically for use on book section portions and journal articles of | |
ClinicalKey. It will not work on book overview pages or journal table of contents pages. | |
NB: url and doc.location.href are different. I think it is because of the way clinicalkey redirects. | |
Replicate by going to a content page (/content/book/...) and then going to a broswing page (/browse/book/...). | |
URL remains the page of the previous page (content page) and doc.location.href is the current page (as it should be). | |
Hence, I never use url and change its content to doc.location.href in detectWeb, the only function that uses the url | |
To get journal article metadata, Publisher Item Identifier (PIIs) are extracted from the Scopus EID | |
and queried using the CrossRef REST API. | |
Documentation at: | |
https://github.com/CrossRef/rest-api-doc | |
CrossReff REST API Return Values at: | |
https://github.com/CrossRef/rest-api-doc/blob/master/api_format.md | |
*/ | |
function detectWeb(doc, url) { | |
//see NB above for explanation | |
url = doc.location.href; | |
//contentType depends on url, which is present, but rest of site is loaded via ajax (I think) | |
//monitor dom and reset if changes | |
var jsession = doc.getElementById('jsessionid'); | |
if (jsession){ | |
Zotero.monitorDOMChanges(jsession, {attributes:true}); | |
if (!jsession.value){ | |
return; | |
} | |
} | |
var contentType; | |
//contains /content/book/ and does not contain login? | |
if (url.indexOf("/content/book/") != -1 && url.indexOf("login?") == -1){ | |
contentType = "bookSection"; | |
} | |
//similar structure to above | |
else if (url.indexOf('/browse/book/') != -1 && url.indexOf("login?") == -1){ | |
contentType = "book"; | |
} | |
// similar structure to above, for journal articles | |
else if (url.indexOf('/content/journal/') != -1 && url.indexOf("login?") == -1){ | |
contentType = "journalArticle"; | |
} | |
//contentType not set | |
if (!contentType){ | |
return; | |
} | |
return contentType; | |
} | |
function doWeb(doc, url){ | |
var contentType = detectWeb(doc, url); | |
//if book section | |
if (contentType == 'bookSection'){ | |
var newItem = new Zotero.Item(contentType); | |
newItem = scrapeBookSection(doc, newItem); | |
//pdf (if present) | |
var pdfLink = getPDFLink(doc); | |
if (pdfLink) { | |
newItem.attachments.push({ | |
url:pdfLink, | |
title:"Book Section PDF", | |
mimeType:"application/pdf" | |
}); | |
} | |
//populate common attributes | |
//url, see NB above for explanation as to why url is NOT used | |
newItem.url = doc.location.href; | |
newItem.complete(); | |
} | |
//if book, use ISBN translator | |
//borrowed from amazon translator | |
else if (contentType == 'book'){ | |
var isbn = ZU.xpath(doc, "//button/@data-metadata-isbn"); | |
if (!isbn){ | |
return; | |
} | |
isbn = ZU.cleanISBN(isbn[0].value); | |
//use search translator to get metadata from isbn | |
var search = Zotero.loadTranslator("search"); | |
//set translators and search | |
search.setHandler("translators", function(obj, translators) { | |
search.setTranslator(translators); | |
search.setHandler("itemDone", function(obj, lookupItem) { | |
newItem=lookupItem; | |
//update ISBN | |
newItem.ISBN = ZU.cleanISBN(isbn); | |
//Override library catalog | |
newItem.libraryCatalog = "Clinical Key"; | |
//update url, see NB for rationale why url not used | |
newItem.url = doc.location.href; | |
}); | |
search.translate(); | |
}); | |
//no need to override error handler | |
//save item | |
search.setHandler("done", function() { | |
newItem.complete(); | |
}); | |
search.setSearch({ ISBN: isbn }); | |
search.getTranslators(); | |
} | |
else if (contentType == 'journalArticle') { | |
var eid; | |
var pii; | |
try { | |
eid = url.split('/'); | |
pii = eid.pop().slice(7); | |
if (!/^S(\d{15}X|\d{16})/.test(pii)){ | |
throw new Error('PII from url failed. Trying Xpath'); | |
} | |
} catch(e) { | |
Zotero.debug(e); | |
eid = ZU.xpathText(doc, "//ul/@data-eid"); | |
pii = eid.slice(7); | |
if (!/^S(\d{15}X|\d{16})/.test(pii)){ | |
throw new Error('PII from Xpath failed'); | |
} | |
} | |
queryCrossRef(pii, doc); | |
} | |
} | |
//Search & Processing based on CrossRef.js translator | |
function queryCrossRef (pii, doc){ | |
crossRefQuery = 'http://api.crossref.org/works?query=' + pii; | |
//TODO: implement API version request | |
//acceptHeader = {'Accept': 'application/vnd.crossref-api-message+json; version=1.0'} | |
ZU.doGet(crossRefQuery, function(responseText) { | |
processCrossRefREST(responseText, doc); | |
}); | |
} | |
function processCrossRefREST(jsonOutput, doc){ | |
var jsonParsed = JSON.parse(jsonOutput); | |
if (jsonParsed['message']['total-results'] > 1) { | |
// Multiple results shouldn't occur as pii is unique | |
// handle only first returned object just in case | |
Zotero.debug('Returned multiple results. Continue processing first'); | |
} | |
else if (jsonParsed['message']['total-results'] == 0) { | |
// If the search failed to find results | |
Zotero.debug('Crossref API failed to find query match'); | |
return; | |
} | |
if (!/^1/.test(jsonParsed['message-version'])){ | |
// check that the API version is compatible with this translator | |
// translator currently written according to v1 | |
Zotero.debug('Request returned wrong API version'); | |
} | |
// shorten JSON to the single reference | |
var ref = jsonParsed['message']['items'][0]; | |
if (ref['type'] == 'journal-article') { | |
// prep for CSL JSON translator | |
ref['type'] = 'article-journal'; | |
} else if (ref['type'] != 'journal-article') { | |
// log the unexpected | |
Zotero.debug('Returned unexpected reference type'); | |
} | |
// use CSL JSON translator | |
var text = JSON.stringify(ref); | |
var trans = Zotero.loadTranslator('import'); | |
trans.setTranslator('bc03b4fe-436d-4a1f-ba59-de4d2d7a63f7'); | |
trans.setString(text); | |
//Attempt to download fulltext PDF | |
var pdfLink = getPDFLink(doc); | |
trans.setHandler('itemDone', function(obj, item) { | |
if (pdfLink) | |
item.attachments.push({ | |
url:pdfLink, | |
title:"Full Text PDF", | |
mimeType:"application/pdf" | |
}); | |
item.complete(); | |
}); | |
trans.translate(); | |
} | |
function getPDFLink(doc) { | |
var pdfLink = doc.getElementsByClassName('x-pdf')[0].href; | |
if (!pdfLink) { | |
pdfLink = ZU.xpathText(doc, './/*[@data-action="pdfDownload"]/@href'); | |
} else if (!pdfLink) { | |
pdfLink = ZU.xpathText(doc, './/*[@action="download"]/@href'); | |
} | |
return pdfLink; | |
} | |
function scrapeBookSection(doc, item){ | |
//book title | |
var bookTitle = ZU.xpathText(doc, '//*[@data-once-text="XocsCtrl.title"]'); | |
item.bookTitle = bookTitle; | |
//section title | |
var title = ZU.xpathText(doc, '//*[@ng-bind-html="ContentCtrl.title"]'); | |
item.title = title; | |
//authors | |
var authorsList = ZU.xpath(doc, '//ul[@ng-bind-html="XocsCtrl.authorsHtml"]/li/a'); | |
for (var i = 0;i<authorsList.length;i++){ | |
var author = authorsList[i].innerHTML; | |
if (author.indexOf("<") != -1){ | |
author = author.split("<")[0]; | |
} | |
item.creators.push(Zotero.Utilities.cleanAuthor(author, 'author')); | |
} | |
//chapter and page metadata | |
var chapterAndPages = ZU.xpathText(doc,'//p[@class="source ng-binding"]'); | |
//make pattern that should capter pages if present | |
//matches both xxx-xxx (length of #s not important) | |
//and xxx-xxx.eY | |
var pagesPattern = /\s\d+-\d+(\.e\d+)?/; | |
var pagesMatch = chapterAndPages.match(pagesPattern); | |
if (pagesMatch){ | |
//get whole regex match | |
item.pages = pagesMatch[0]; | |
} | |
//make pattern that will match to the chapter number | |
var chapterPattern = /chapter\s(\d+)/i | |
var chapterMatch = chapterAndPages.match(chapterPattern); | |
if (chapterMatch){ | |
//get match within first group | |
var chapterNumber = chapterMatch[1]; | |
item.notes.push({note:"Chapter: "+chapterNumber}); | |
} | |
//ISBN metadata | |
var isbn = ZU.xpath(doc, "//button/@data-metadata-isbn"); | |
if (isbn){ | |
var isbnNo = isbn[0].value; | |
item.ISBN = isbn; | |
} | |
//edition metadata | |
var edition = ZU.xpathText(doc, '//*[@data-once-text="XocsCtrl.edition"]').split(/edition/i)[0].trim(); | |
//convert to number for correct zotero citation handling | |
item.edition = textToNumber(edition); | |
//publisher metadata | |
var datePub = ZU.xpathText(doc, '//*[@data-once-text="XocsCtrl.copyright"]'); | |
var datePattern = /\d{4}/g; | |
var dateMatch = datePub.match(datePattern); | |
if (dateMatch){ | |
item.date = dateMatch[0]; | |
} | |
if (datePub.indexOf("imprint") != -1){ | |
var imprintPattern = /by\s(.*),.*imprint\sof\s(.*)\sInc/i; | |
var imprintMatch = datePub.match(imprintPattern); | |
if (imprintMatch){ | |
//expected number of matches | |
if (imprintMatch.length == 3){ | |
item.publisher = imprintMatch[2]+"/"+imprintMatch[1]; | |
} | |
//added for robustness | |
else { | |
var imprintPublisher=imprintMatch[0].replace("by","").trim(); | |
item.publisher=imprintPublisher; | |
} | |
} | |
} | |
else { | |
var publisherPattern = /by\s(.*?)(,)?\s/; | |
var publisherMatch = datePub.match(publisherPattern); | |
if (publisherMatch){ | |
//get first matched group, between by and , or whitespace | |
item.publisher=publisherMatch[1]; | |
} | |
} | |
if (datePub.match(/elsevier/i)){ | |
item.place = "Philadelphia, PA"; | |
} | |
return item; | |
} | |
//Converts ordinal text to number | |
//Only converting up to 31 | |
//E.g., text=first -> 1 | |
//E.g., Twenty-Second -> 22 | |
function textToNumber(text){ | |
var textarr = [ | |
"first", | |
"second", | |
"third", | |
"fourth", | |
"fifth", | |
"sixth", | |
"seventh", | |
"eighth", | |
"ninth", | |
"tenth", | |
"eleventh", | |
"twelfth", | |
"thirteenth", | |
"fourteenth", | |
"fifteenth", | |
"sixteenth", | |
"seventeenth", | |
"eighteenth", | |
"nineteenth", | |
"twentieth", | |
"twenty-first", | |
"twenty-second", | |
"twenty-third", | |
"twenty-fourth", | |
"twenty-fifth", | |
"twenty-sixth", | |
"twenty-seventh", | |
"twenty-eighth", | |
"twenty-ninth", | |
"thirtieth", | |
"thirty-first" | |
]; | |
var number = textarr.indexOf(text.toLowerCase()); | |
//shift from 0 to 1 based indexing | |
return (number != -1)? number + 1 : text; | |
} |