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.js
Find file
Copy path
Fetching contributors…
Cannot retrieve contributors at this time
{ | |
"translatorID": "11645bd1-0420-45c1-badb-53fb41eeb753", | |
"translatorType": 8, | |
"label": "Crossref", | |
"creator": "Simon Kornblith", | |
"target": "^https?://partneraccess\\.oclc\\.org/", | |
"minVersion": "2.1.9", | |
"maxVersion": null, | |
"priority": 90, | |
"inRepository": true, | |
"browserSupport": "gcsv", | |
"lastUpdated": "2019-01-07 08:14:17" | |
} | |
/* CrossRef uses unixref; documentation at http://www.crossref.org/schema/documentation/unixref1.0/unixref.html */ | |
var ns; | |
/********************** | |
* Utilitiy Functions * | |
**********************/ | |
function innerXML(n) { | |
var escapedXMLcharacters = { | |
'&': '&', | |
'"': '"', | |
'<': '<', | |
'>': '>' | |
}; | |
var xmlSerializer = new XMLSerializer(); | |
return xmlSerializer.serializeToString(n) //outer XML | |
.replace(/^[^>]*>|<[^<]*$/g, '') | |
.replace(/("|<|>|&)/g, | |
function(str, item) { | |
return escapedXMLcharacters[item]; | |
} | |
); | |
} | |
var markupRE = /<(\/?)(\w+)[^<>]*>/gi; | |
var supportedMarkup = ['i', 'b', 'sub', 'sup', 'span', 'sc']; | |
var transformMarkup = { | |
'scp': { | |
open: '<span style="font-variant:small-caps;">', | |
close: '</span>' | |
} | |
}; | |
function removeUnsupportedMarkup(text) { | |
return text.replace(/<!\[CDATA\[([\s\S]*?)\]\]>/g, '$1') // Remove CDATA markup | |
.replace(markupRE, function(m, close, name) { | |
if (supportedMarkup.indexOf(name.toLowerCase()) != -1) { | |
return m; | |
} | |
var newMarkup = transformMarkup[name.toLowerCase()]; | |
if (newMarkup) { | |
return close ? newMarkup.close : newMarkup.open; | |
} | |
return ''; | |
}); | |
} | |
function detectSearch(item) { | |
// query: should we make this more forgiving? | |
if (item.itemType === "journalArticle" || item.DOI) { | |
// Don't try to look up PMIDs | |
if (item.contextObject && item.contextObject.startsWith('rft_id=info:pmid')) { | |
return false; | |
} | |
return true; | |
} | |
return false; | |
} | |
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(node, item, typeOverrideMap) { | |
var contributors = ZU.xpath(node, 'c:contributors/c:organization | c:contributors/c:person_name', ns); | |
for (var i in contributors) { | |
var creatorXML = contributors[i]; | |
var creator = {}; | |
var role = creatorXML.getAttribute("contributor_role"); | |
if (typeOverrideMap && typeOverrideMap[role] !== undefined) { | |
creator.creatorType = typeOverrideMap[role]; | |
} else if (role === "author" || role === "editor" || role === "translator") { | |
creator.creatorType = role; | |
} else { | |
creator.creatorType = "contributor"; | |
} | |
if (!creator.creatorType) continue; | |
if (creatorXML.nodeName === "organization") { | |
creator.fieldMode = 1; | |
creator.lastName = creatorXML.textContent; | |
} else if (creatorXML.nodeName === "person_name") { | |
creator.firstName = fixAuthorCapitalization(ZU.xpathText(creatorXML, 'c:given_name', ns)); | |
creator.lastName = fixAuthorCapitalization(ZU.xpathText(creatorXML, 'c:surname', ns)); | |
if (!creator.firstName) creator.fieldMode = 1; | |
} | |
item.creators.push(creator); | |
} | |
} | |
function processCrossRef(xmlOutput) { | |
// XPath does not give us the ability to use the same XPaths regardless of whether or not | |
// there is a namespace, so we add an element to make sure that there will always be a | |
// namespace. | |
xmlOutput = '<xml xmlns="http://www.example.com/">'+xmlOutput.replace(/<\?xml[^>]*\?>/, "")+"</xml>"; | |
// parse XML with E4X | |
try { | |
var parser = new DOMParser(); | |
var doc = parser.parseFromString(xmlOutput, "text/xml"); | |
} catch(e) { | |
Zotero.debug(e); | |
return false; | |
} | |
// determine appropriate namespace | |
ns = {"c":"http://www.crossref.org/xschema/1.1", "x":"http://www.example.com/"}; | |
var doiRecords = ZU.xpath(doc, '/x:xml/c:doi_records/c:doi_record', ns); | |
if (!doiRecords.length) { | |
ns.c = "http://www.crossref.org/xschema/1.0"; | |
doiRecords = ZU.xpath(doc, '/x:xml/c:doi_records/c:doi_record', ns); | |
if (!doiRecords.length) { | |
// this means that the original document was un-namespaced | |
ns.c = "http://www.example.com/"; | |
doiRecords = ZU.xpath(doc, '/c:xml/c:doi_records/c:doi_record', ns); | |
if (!doiRecords.length) { | |
throw new Error("No records found"); | |
return; | |
} | |
} | |
} | |
var doiRecord = doiRecords[0]; | |
// ensure this isn't an error | |
var errorString = ZU.xpathText(doiRecord, 'c:crossref/c:error', ns); | |
if (errorString !== null) { | |
throw errorString; | |
return false; | |
} | |
var itemXML, item, refXML, metadataXML, seriesXML; | |
if ((itemXML = ZU.xpath(doiRecord, 'c:crossref/c:journal', ns)).length) { | |
item = new Zotero.Item("journalArticle"); | |
refXML = ZU.xpath(itemXML, 'c:journal_article', ns); | |
metadataXML = ZU.xpath(itemXML, 'c:journal_metadata', ns); | |
item.publicationTitle = ZU.xpathText(metadataXML, 'c:full_title[1]', ns); | |
item.journalAbbreviation = ZU.xpathText(refXML, 'c:abbrev_title[1]', ns); | |
item.volume = ZU.xpathText(itemXML, 'c:journal_issue/c:journal_volume/c:volume', ns); | |
item.issue = ZU.xpathText(itemXML, 'c:journal_issue/c:journal_volume/c:issue', ns); | |
// Sometimes the <issue> tag is not nested inside the volume tag; see 10.1007/BF00938486 | |
if (!item.issue) | |
item.issue = ZU.xpathText(itemXML, 'c:journal_issue/c:issue', ns); | |
} else if ((itemXML = ZU.xpath(doiRecord, 'c:crossref/c:report-paper', ns)).length) { | |
// Report Paper | |
// Example: doi: 10.4271/2010-01-0907 | |
// https://www.crossref.org/openurl/?pid=zter:zter321&url_ver=Z39.88-2004&rft_id=info:doi/10.4271/2010-01-0907&format=unixref&redirect=false | |
item = new Zotero.Item("report"); | |
refXML = ZU.xpath(itemXML, 'c:report-paper_metadata', ns); | |
if (refXML.length===0) { | |
// Example doi: 10.1787/5jzb6vwk338x-en | |
// https://www.crossref.org/openurl/?pid=zter:zter321&url_ver=Z39.88-2004&&rft_id=info:doi/10.1787/5jzb6vwk338x-en&noredirect=true&format=unixref | |
refXML = ZU.xpath(itemXML, 'c:report-paper_series_metadata', ns); | |
seriesXML = ZU.xpath(refXML, 'c:series_metadata', ns); | |
} | |
metadataXML = refXML; | |
item.reportNumber = ZU.xpathText(refXML, 'c:publisher_item/c:item_number', ns); | |
if (!item.reportNumber) item.reportNumber = ZU.xpathText(refXML, 'c:volume', ns); | |
item.institution = ZU.xpathText(refXML, 'c:publisher/c:publisher_name', ns); | |
item.place = ZU.xpathText(refXML, 'c:publisher/c:publisher_place', ns); | |
} else if ((itemXML = ZU.xpath(doiRecord, 'c:crossref/c:book', ns)).length) { | |
// Book chapter | |
// Example: doi: 10.1017/CCOL0521858429.016 | |
// https://www.crossref.org/openurl/?pid=zter:zter321&url_ver=Z39.88-2004&rft_id=info:doi/10.1017/CCOL0521858429.016&format=unixref&redirect=false | |
// Reference book entry | |
// Example: doi: 10.1002/14651858.CD002966.pub3 | |
// https://www.crossref.org/openurl/?pid=zter:zter321&url_ver=Z39.88-2004&rft_id=info:doi/10.1002/14651858.CD002966.pub3&format=unixref&redirect=false | |
// Entire edite book. This should _not_ be imported as bookSection | |
// Example: doi: 10.4135/9781446200957 | |
// https://www.crossref.org/openurl/?pid=zter:zter321&url_ver=Z39.88-2004&&rft_id=info:doi/10.4135/9781446200957&noredirect=true&format=unixref | |
var bookType = itemXML[0].hasAttribute("book_type") ? itemXML[0].getAttribute("book_type") : null; | |
var componentType = ZU.xpathText(itemXML[0], 'c:content_item/@component_type', ns); | |
//is this an entry in a reference book? | |
var isReference = ["reference", "other"].indexOf(bookType) !== -1 | |
&& ["chapter", "reference_entry"].indexOf(componentType) !==-1; | |
//for items that are entry in reference books OR edited book types that have some type of a chapter entry. | |
if ((bookType === "edited_book" && componentType) || isReference) { | |
item = new Zotero.Item("bookSection"); | |
refXML = ZU.xpath(itemXML, 'c:content_item', ns); | |
if (isReference) { | |
metadataXML = ZU.xpath(itemXML, 'c:book_metadata', ns); | |
if (!metadataXML.length) metadataXML = ZU.xpath(itemXML, 'c:book_series_metadata', ns); | |
// TODO: Check book_set_metadata here too, as we do below? | |
item.bookTitle = ZU.xpathText(metadataXML, 'c:titles[1]/c:title[1]', ns); | |
item.seriesTitle = ZU.xpathText(metadataXML, 'c:series_metadata/c:titles[1]/c:title[1]', ns); | |
var metadataSeriesXML = ZU.xpath(metadataXML, 'c:series_metadata', ns); | |
if (metadataSeriesXML.length) parseCreators(metadataSeriesXML, item, {"editor":"seriesEditor"}); | |
} else { | |
metadataXML = ZU.xpath(itemXML, 'c:book_series_metadata', ns); | |
if (!metadataXML.length) metadataXML = ZU.xpath(itemXML, 'c:book_metadata', ns); | |
item.bookTitle = ZU.xpathText(metadataXML, 'c:series_metadata/c:titles[1]/c:title[1]', ns); | |
if (!item.bookTitle) item.bookTitle = ZU.xpathText(metadataXML, 'c:titles[1]/c:title[1]', ns); | |
} | |
// Handle book authors | |
parseCreators(metadataXML, item, {"author":"bookAuthor"}); | |
// Book | |
} else { | |
item = new Zotero.Item("book"); | |
refXML = ZU.xpath(itemXML, 'c:book_metadata', ns); | |
// Sometimes book data is in book_series_metadata | |
// doi: 10.1007/978-1-4419-9164-5 | |
// https://www.crossref.org/openurl/?pid=zter:zter321&url_ver=Z39.88-2004&rft_id=info:doi/10.1007/978-1-4419-9164-5&format=unixref&redirect=false | |
// And sometimes in book_set_metadata | |
// doi: 10.7551/mitpress/9780262533287.003.0006 | |
// https://www.crossref.org/openurl/?pid=zter:zter321&url_ver=Z39.88-2004&&rft_id=info:doi/10.7551/mitpress/9780262533287.003.0006&noredirect=true&format=unixref | |
if (!refXML.length) refXML = ZU.xpath(itemXML, 'c:book_series_metadata', ns); | |
if (!refXML.length) refXML = ZU.xpath(itemXML, 'c:book_set_metadata', ns); | |
metadataXML = refXML; | |
seriesXML = ZU.xpath(refXML, 'c:series_metadata', ns); | |
} | |
item.place = ZU.xpathText(metadataXML, 'c:publisher/c:publisher_place', ns); | |
} else if ((itemXML = ZU.xpath(doiRecord, 'c:crossref/c:standard', ns)).length) { | |
item = new Zotero.Item("report"); | |
refXML = ZU.xpath(itemXML, 'c:standard_metadata', ns); | |
metadataXML = ZU.xpath(itemXML, 'c:standard_metadata', ns); | |
} else if ((itemXML = ZU.xpath(doiRecord, 'c:crossref/c:conference', ns)).length) { | |
item = new Zotero.Item("conferencePaper"); | |
refXML = ZU.xpath(itemXML, 'c:conference_paper', ns); | |
metadataXML = ZU.xpath(itemXML, 'c:proceedings_metadata', ns); | |
seriesXML = ZU.xpath(metadataXML, 'c:proceedings_metadata', ns); | |
item.publicationTitle = ZU.xpathText(metadataXML, 'c:proceedings_title', ns); | |
item.place = ZU.xpathText(itemXML, 'c:event_metadata/c:conference_location', ns); | |
item.conferenceName = ZU.xpathText(itemXML, 'c:event_metadata/c:conference_name', ns); | |
} | |
else if ((itemXML = ZU.xpath(doiRecord, 'c:crossref/c:database', ns)).length) { | |
item = new Zotero.Item("report"); //should be dataset | |
refXML = ZU.xpath(itemXML, 'c:dataset', ns); | |
item.extra = "type: dataset"; | |
metadataXML = ZU.xpath(itemXML, 'c:database_metadata', ns); | |
if (!ZU.xpathText(refXML, 'c:contributors', ns)) { | |
parseCreators(metadataXML, item); | |
} | |
if (!ZU.xpathText(metadataXML, 'c:publisher', ns)) { | |
item.institution = ZU.xpathText(metadataXML, 'c:institution/c:institution_name', ns); | |
} | |
} | |
item.abstractNote = ZU.xpathText(refXML, 'c:description', ns); | |
item.language = ZU.xpathText(metadataXML, './@language', ns); | |
item.ISBN = ZU.xpathText(metadataXML, 'c:isbn', ns); | |
item.ISSN = ZU.xpathText(metadataXML, 'c:issn', ns); | |
item.publisher = ZU.xpathText(metadataXML, 'c:publisher/c:publisher_name', ns); | |
item.edition = ZU.xpathText(metadataXML, 'c:edition_number', ns); | |
if (!item.volume) item.volume = ZU.xpathText(metadataXML, 'c:volume', ns); | |
parseCreators(refXML, item, (item.itemType == 'bookSection' ? {"editor": null} : "author") ); | |
if (seriesXML && seriesXML.length) { | |
parseCreators(seriesXML, item, {"editor":"seriesEditor"}); | |
item.series = ZU.xpathText(seriesXML, 'c:titles[1]/c:title[1]', ns); | |
item.seriesNumber = ZU.xpathText(seriesXML, 'c:series_number', ns); | |
item.reportType = ZU.xpathText(seriesXML, 'c:titles[1]/c:title[1]', ns); | |
} | |
//prefer article to journal metadata and print to other dates | |
var pubDateNode = ZU.xpath(refXML, 'c:publication_date[@media_type="print"]', ns); | |
if (!pubDateNode.length) pubDateNode = ZU.xpath(refXML, 'c:publication_date', ns); | |
if (!pubDateNode.length) pubDateNode = ZU.xpath(metadataXML, 'c:publication_date[@media_type="print"]', ns); | |
if (!pubDateNode.length) pubDateNode = ZU.xpath(metadataXML, 'c:publication_date', ns); | |
//dataset | |
if (!pubDateNode.length) pubDateNode = ZU.xpath(refXML, 'c:database_date/c:publication_date', ns); | |
if (!pubDateNode.length) pubDateNode = ZU.xpath(metadataXML, 'c:database_date/c:publication_date', ns); | |
if (pubDateNode.length) { | |
var year = ZU.xpathText(pubDateNode[0], 'c:year', ns); | |
var month = ZU.xpathText(pubDateNode[0], 'c:month', ns); | |
var day = ZU.xpathText(pubDateNode[0], 'c:day', ns); | |
if (year) { | |
if (month) { | |
if (day) { | |
item.date = year+"-"+month+"-"+day; | |
} else { | |
item.date = month+"/"+year; | |
} | |
} else { | |
item.date = year; | |
} | |
} | |
} | |
var pages = ZU.xpath(refXML, 'c:pages[1]', ns); | |
if (pages.length) { | |
item.pages = ZU.xpathText(pages, 'c:first_page[1]', ns); | |
var lastPage = ZU.xpathText(pages, 'c:last_page[1]', ns); | |
if (lastPage) item.pages += "-"+lastPage; | |
} | |
item.DOI = ZU.xpathText(refXML, 'c:doi_data/c:doi', ns); | |
//add DOI to extra for unsupprted items | |
if (item.DOI && !ZU.fieldIsValidForType("DOI", item.itemType)) { | |
if (item.extra){ | |
item.extra += "\nDOI: " + item.DOI; | |
} | |
else { | |
item.extra = "DOI: " + item.DOI; | |
} | |
} | |
item.url = ZU.xpathText(refXML, 'c:doi_data/c:resource', ns); | |
var title = ZU.xpath(refXML, 'c:titles[1]/c:title[1]', ns)[0]; | |
if (!title) { | |
title = ZU.xpath(metadataXML, 'c:titles[1]/c:title[1]', ns)[0]; | |
} | |
if (title) { | |
item.title = ZU.trimInternal( | |
removeUnsupportedMarkup(innerXML(title)) | |
); | |
var subtitle = ZU.xpath(refXML, 'c:titles[1]/c:subtitle[1]', ns)[0]; | |
if (subtitle) { | |
item.title += ': ' + ZU.trimInternal( | |
removeUnsupportedMarkup(innerXML(subtitle)) | |
); | |
} | |
} | |
//Zotero.debug(JSON.stringify(item, null, 4)); | |
//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 (var 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.complete(); | |
return true; | |
} | |
function doSearch(item) { | |
if (item.contextObject) { | |
var co = item.contextObject; | |
if (co.indexOf("url_ver=") == -1) { | |
co = "url_ver=Z39.88-2004&"+co; | |
} | |
} else if (item.DOI) { | |
var co = "url_ver=Z39.88-2004&&rft_id=info:doi/"+ZU.cleanDOI(item.DOI.toString()); | |
} else { | |
var co = Zotero.Utilities.createContextObject(item); | |
} | |
ZU.doGet("https://www.crossref.org/openurl/?pid=zter:zter321&"+co+"&noredirect=true&format=unixref", function(responseText) { | |
processCrossRef(responseText); | |
}); | |
} | |
/** BEGIN TEST CASES **/ | |
var testCases = [ | |
{ | |
"type": "search", | |
"input": { | |
"DOI":"10.1017/CCOL0521858429.016" | |
}, | |
"items": [ | |
{ | |
"itemType": "bookSection", | |
"creators": [ | |
{ | |
"creatorType": "editor", | |
"firstName": "John", | |
"lastName": "Rodden" | |
}, | |
{ | |
"creatorType": "author", | |
"firstName": "Christopher", | |
"lastName": "Hitchens" | |
} | |
], | |
"notes": [], | |
"tags": [], | |
"seeAlso": [], | |
"attachments": [], | |
"bookTitle": "The Cambridge Companion to George Orwell", | |
"place": "Cambridge", | |
"ISBN": "9781139001472", | |
"publisher": "Cambridge University Press", | |
"pages": "201-207", | |
"date": "2007", | |
"extra": "DOI: 10.1017/CCOL0521858429.016", | |
"DOI": "10.1017/CCOL0521858429.016", | |
"url": "http://universitypublishingonline.org/ref/id/companions/CBO9781139001472A019", | |
"title": "Why Orwell still matters", | |
"libraryCatalog": "CrossRef" | |
} | |
] | |
}, | |
{ | |
"type": "search", | |
"input": { | |
"DOI":"10.1057/9780230391116.0016" | |
}, | |
"items": [ | |
{ | |
"itemType": "bookSection", | |
"creators": [ | |
{ | |
"creatorType": "editor", | |
"firstName": "Claus-Christian W.", | |
"lastName": "Szejnmann" | |
}, | |
{ | |
"creatorType": "editor", | |
"firstName": "Maiken", | |
"lastName": "Umbach" | |
}, | |
{ | |
"creatorType": "author", | |
"firstName": "Oliver", | |
"lastName": "Werner" | |
} | |
], | |
"notes": [], | |
"tags": [], | |
"seeAlso": [], | |
"attachments": [], | |
"bookTitle": "Heimat, Region, and Empire", | |
"ISBN": "9780230391116", | |
"publisher": "Palgrave Macmillan", | |
"language": "en", | |
"date": "2012-10-17", | |
"extra": "DOI: 10.1057/9780230391116.0016", | |
"DOI": "10.1057/9780230391116.0016", | |
"url": "http://www.palgraveconnect.com/doifinder/10.1057/9780230391116.0016", | |
"title": "Conceptions, Competences and Limits of German Regional Planning during the Four Year Plan, 1936–1940", | |
"libraryCatalog": "CrossRef" | |
} | |
] | |
}, | |
{ | |
"type": "search", | |
"input": { | |
"DOI":"10.2747/1539-7216.50.2.197" | |
}, | |
"items": [ | |
{ | |
"itemType": "journalArticle", | |
"creators": [ | |
{ | |
"creatorType": "author", | |
"firstName": "Kam Wing", | |
"lastName": "Chan" | |
} | |
], | |
"notes": [], | |
"tags": [], | |
"seeAlso": [], | |
"attachments": [], | |
"date": "2009-3-1", | |
"DOI": "10.2747/1539-7216.50.2.197", | |
"url": "http://www.tandfonline.com/doi/abs/10.2747/1539-7216.50.2.197", | |
"title": "The Chinese <i>Hukou</i> System at 50", | |
"issue": "2", | |
"ISSN": "1538-7216", | |
"publicationTitle": "Eurasian Geography and Economics", | |
"volume": "50", | |
"pages": "197-221", | |
"libraryCatalog": "CrossRef" | |
} | |
] | |
}, | |
{ | |
"type": "search", | |
"input": { | |
"DOI":"10.1109/ISSCC.2017.7870285" | |
}, | |
"items": [ | |
{ | |
"itemType": "conferencePaper", | |
"creators": [ | |
{ | |
"creatorType": "author", | |
"firstName": "Pen-Jui", | |
"lastName": "Peng" | |
}, | |
{ | |
"creatorType": "author", | |
"firstName": "Jeng-Feng", | |
"lastName": "Li" | |
}, | |
{ | |
"creatorType": "author", | |
"firstName": "Li-Yang", | |
"lastName": "Chen" | |
}, | |
{ | |
"creatorType": "author", | |
"firstName": "Jri", | |
"lastName": "Lee" | |
} | |
], | |
"notes": [], | |
"tags": [], | |
"seeAlso": [], | |
"attachments": [], | |
"publicationTitle": "2017 IEEE International Solid-State Circuits Conference (ISSCC)", | |
"place": "San Francisco, CA, USA", | |
"conferenceName": "2017 IEEE International Solid- State Circuits Conference - (ISSCC)", | |
"abstractNote": null, | |
"language": null, | |
"ISBN": "978-1-5090-3758-2", | |
"ISSN": null, | |
"publisher": "IEEE", | |
"edition": null, | |
"volume": null, | |
"date": "2/2017", | |
"pages": "110-111", | |
"DOI": "10.1109/ISSCC.2017.7870285", | |
"url": "http://ieeexplore.ieee.org/document/7870285/", | |
"title": "6.1 A 56Gb/s PAM-4/NRZ transceiver in 40nm CMOS" | |
} | |
] | |
} | |
] | |
/** END TEST CASES **/ | |