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/Crossref-REST.js
Find file
Copy path
Fetching contributors…
Cannot retrieve contributors at this time
{ | |
"translatorID": "0a61e167-de9a-4f93-a68a-628b48855909", | |
"translatorType": 8, | |
"label": "Crossref REST", | |
"creator": "Martynas Bagdonas", | |
"target": "", | |
"minVersion": "5.0.0", | |
"maxVersion": null, | |
"priority": 90, | |
"inRepository": true, | |
"browserSupport": "gcsibv", | |
"lastUpdated": "2018-06-16 12:00:00" | |
} | |
/* | |
***** BEGIN LICENSE BLOCK ***** | |
Copyright © 2018 | |
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 ***** | |
*/ | |
// Based on CrossRef.js (by Simon Kornblith), which uses OpenURL API | |
// This translator uses the newer REST API | |
// https://github.com/Crossref/rest-api-doc | |
// https://github.com/Crossref/rest-api-doc/blob/master/api_format.md | |
// REST API documentation not always reflect the actual API | |
// and some fields are undocumented. | |
// All Crossref item types can be retrieved at http://api.crossref.org/types | |
function removeUnsupportedMarkup(text) { | |
let markupRE = /<(\/?)(\w+)[^<>]*>/gi; | |
let supportedMarkup = ['i', 'b', 'sub', 'sup', 'sc']; | |
let transformMarkup = { | |
'scp': { | |
open: '<span style="font-variant:small-caps;">', | |
close: '</span>' | |
} | |
}; | |
return text.replace(/<!\[CDATA\[([\s\S]*?)\]\]>/g, '$1') // Remove CDATA markup | |
.replace(markupRE, function (m, close, name) { | |
name = name.toLowerCase(); | |
if (supportedMarkup.includes(name)) { | |
return (close ? '</' : '<') + name + '>'; | |
} | |
let newMarkup = transformMarkup[name.toLowerCase()]; | |
if (newMarkup) { | |
return close ? newMarkup.close : newMarkup.open; | |
} | |
return ''; | |
}); | |
} | |
function fixAuthorCapitalization(string) { | |
// Try to use capitalization function from Zotero Utilities, | |
// because the current one doesn't support unicode names. | |
// Can't fix this either because ZU.XRegExp.replace is | |
// malfunctioning when calling from translators. | |
if (ZU.capitalizeName) return ZU.capitalizeName(string); | |
if (typeof string === "string" && string.toUpperCase() === string) { | |
string = string.toLowerCase().replace(/\b[a-z]/g, function (m) { | |
return m[0].toUpperCase(); | |
}); | |
} | |
return string; | |
} | |
function parseCreators(result, item, typeOverrideMap) { | |
let types = ['author', 'editor', 'chair', 'translator']; | |
for (let i = 0; i < types.length; i++) { | |
let type = types[i]; | |
if (result[type]) { | |
let creatorType = null; | |
if (typeOverrideMap && typeOverrideMap[type] !== undefined) { | |
creatorType = typeOverrideMap[type]; | |
} | |
else if (type === "author" || type === "editor" || type === "translator") { | |
creatorType = type; | |
} | |
else { | |
creatorType = "contributor"; | |
} | |
if (!creatorType) continue; | |
for (let j = 0; j < result[type].length; j++) { | |
let creator = {}; | |
creator.creatorType = creatorType; | |
if (result[type].name) { // Organization | |
creator.fieldMode = 1; | |
creator.lastName = result[type][j].name; | |
} | |
else { | |
creator.firstName = fixAuthorCapitalization(result[type][j].given); | |
creator.lastName = fixAuthorCapitalization(result[type][j].family); | |
if (!creator.firstName) creator.fieldMode = 1; | |
} | |
item.creators.push(creator); | |
} | |
} | |
} | |
} | |
function processCrossref(json) { | |
json = JSON.parse(json); | |
for (let i = 0; i < json.message.items.length; i++) { | |
let result = json.message.items[i]; | |
let item = null; | |
// Journal article | |
if (['journal-article'].includes(result.type)) { | |
item = new Zotero.Item("journalArticle"); | |
if (result['container-title']) item.publicationTitle = result['container-title'][0]; | |
if (result['short-container-title'] | |
&& result['short-container-title'][0] !== result['container-title'][0]) { | |
item.journalAbbreviation = result['short-container-title'][0]; | |
} | |
item.volume = result.volume; | |
item.issue = result.issue; | |
if (result.ISBN) item.ISBN = result.ISBN[0]; | |
if (result.ISSN) item.ISSN = result.ISSN[0]; | |
} | |
// Book | |
else if (['book', 'book-series', 'book-set', 'book-track', 'monograph', 'reference-book'] | |
.includes(result.type)) { | |
item = new Zotero.Item("book"); | |
item.publisher = result.publisher; | |
item.place = result['publisher-location']; | |
if (result.ISBN) item.ISBN = result.ISBN[0]; | |
} | |
// Book section | |
else if (['book-chapter', 'book-part', 'book-section', 'reference-entry'] | |
.includes(result.type)) { | |
item = new Zotero.Item("bookSection"); | |
item.publisher = result.publisher; | |
item.place = result['publisher-location']; | |
if (result.ISBN) item.ISBN = result.ISBN[0]; | |
} | |
// Report | |
else if (['dataset', 'posted-content', 'report', 'report-series', 'standard'] | |
.includes(result.type)) { | |
item = new Zotero.Item("report"); | |
item.institution = result.publisher; | |
item.place = result['publisher-location']; | |
item.seriesTitle = result['container-title']; | |
} | |
// Conference paper | |
else if (['proceedings-article'].includes(result.type)) { | |
item = new Zotero.Item("conferencePaper"); | |
item.proceedingsTitle = result['container-title']; | |
item.publisher = result.publisher; | |
if (result.event) { | |
item.conferenceName = result.event.name; | |
item.place = result.event.location; | |
} | |
if (result.ISBN) item.ISBN = result.ISBN[0]; | |
} | |
// Thesis | |
else if (['dissertation'].includes(result.type)) { | |
item = new Zotero.Item("thesis"); | |
item.university = result.publisher; | |
item.place = result['publisher-location']; | |
} | |
else { | |
return; | |
} | |
// edited-book, standard-series - ignore, because Crossref has zero results for this type | |
// component, journal, journal-issue, journal-volume, other, proceedings - ignore, | |
// because Zotero doesn't have equivalent item types. | |
item.abstractNote = result.abstract; | |
parseCreators(result, item); | |
// Contains the earliest of: published-online, published-print, content-created | |
let pubDate = result['issued']; | |
if (pubDate && pubDate['date-parts'][0]) { | |
let year = pubDate['date-parts'][0][0]; | |
let month = pubDate['date-parts'][0][1]; | |
let day = pubDate['date-parts'][0][2]; | |
if (year) { | |
if (month) { | |
if (day) { | |
item.date = year + "-" + month + "-" + day; | |
} | |
else { | |
item.date = month + "/" + year; | |
} | |
} | |
else { | |
item.date = year; | |
} | |
} | |
} | |
item.pages = result.page; | |
if (result.DOI) { | |
if (ZU.fieldIsValidForType('DOI', item.itemType)) { | |
item.DOI = result.DOI; | |
} | |
// add DOI to extra for unsupprted items | |
else { | |
if (item.extra) { | |
item.extra += '\nDOI: ' + result.DOI; | |
} | |
else { | |
item.extra = 'DOI: ' + result.DOI; | |
} | |
} | |
} | |
// result.URL is always http://dx.doi.org/.. | |
if (result.link && result.link.URL) item.url = result.link.URL; | |
if (result.title && result.title[0]) { | |
item.title = result.title[0]; | |
if (result.subtitle && result.subtitle[0]) { | |
// Don't duplicate subtitle if it already exists in title | |
if (item.title.toLowerCase().indexOf(result.subtitle[0].toLowerCase()) < 0) { | |
item.title += ': ' + result.subtitle[0]; | |
} | |
} | |
item.title = removeUnsupportedMarkup(item.title); | |
} | |
// Check if there are potential issues with character encoding and try to fix it | |
// e.g. 10.1057/9780230391116.0016 (en dash in title is presented as <control><control>â) | |
for (let field in item) { | |
if (typeof item[field] !== 'string') continue; | |
// Check for control characters that should never be in strings from Crossref | |
if (/[\u007F-\u009F]/.test(item[field])) { | |
item[field] = decodeURIComponent(escape(item[field])); | |
} | |
item[field] = ZU.unescapeHTML(item[field]); | |
} | |
item.libraryCatalog = 'Crossref'; | |
item.complete(); | |
} | |
} | |
function detectSearch(item) { | |
return false; | |
} | |
function doSearch(item) { | |
// Reduce network traffic by selecting only required fields | |
let selectedFields = [ | |
'type', | |
'container-title', | |
'short-container-title', | |
'volume', | |
'issue', | |
'ISBN', | |
'ISSN', | |
'publisher', | |
'publisher-location', | |
'event', | |
'abstract', | |
'issued', | |
'page', | |
'DOI', | |
'link', | |
'title', | |
'subtitle', | |
'author', | |
'editor', | |
'chair', | |
'translator' | |
]; | |
let query = null; | |
if (item.DOI) { | |
if (Array.isArray(item.DOI)) { | |
query = '?filter=doi:' + item.DOI.map(x => ZU.cleanDOI(x)).filter(x => x).join(',doi:'); | |
} else { | |
query = '?filter=doi:' + ZU.cleanDOI(item.DOI); | |
} | |
} | |
else if (item.query) { | |
query = '?query.bibliographic=' + encodeURIComponent(item.query); | |
} | |
else return; | |
query += '&select=' + selectedFields.join(','); | |
if (Z.getHiddenPref('CrossrefREST.email')) { | |
query += '&mailto=' + Z.getHiddenPref('CrossrefREST.email'); | |
} | |
ZU.doGet('https://api.crossref.org/works/' + query, function (responseText) { | |
processCrossref(responseText); | |
}); | |
} | |
/** BEGIN TEST CASES **/ | |
var testCases = []; | |
/** END TEST CASES **/ |