Permalink
Join GitHub today
GitHub is home to over 36 million developers working together to host and review code, manage projects, and build software together.
Sign up
Fetching contributors…
Cannot retrieve contributors at this time
{ | |
"translatorID": "e7e01cac-1e37-4da6-b078-a0e8343b0e98", | |
"label": "unAPI", | |
"creator": "Simon Kornblith", | |
"target": "", | |
"minVersion": "2.1", | |
"maxVersion": "", | |
"priority": 300, | |
"inRepository": true, | |
"translatorType": 4, | |
"browserSupport": "gcsbv", | |
"lastUpdated": "2018-05-12 15:58:17" | |
} | |
var RECOGNIZABLE_FORMATS = ["rdf_zotero", "rdf_bibliontology", "marc", | |
"unimarc", "marcxml", "mods", "ris", "refer", "bibtex", "rdf_dc"]; | |
var FORMAT_GUIDS = { | |
"rdf_zotero":"5e3ad958-ac79-463d-812b-a86a9235c28f", | |
"rdf_bibliontology":"14763d25-8ba0-45df-8f52-b8d1108e7ac9", | |
"mods":"0e2235e7-babf-413c-9acf-f27cce5f059c", | |
"marc":"a6ee60df-1ddc-4aae-bb25-45e0537be973", | |
"unimarc":"a6ee60df-1ddc-4aae-bb25-45e0537be973", | |
"marcxml":"edd87d07-9194-42f8-b2ad-997c4c7deefd", | |
"ris":"32d59d2d-b65a-4da4-b0a3-bdd3cfb979e7", | |
"refer":"881f60f2-0802-411a-9228-ce5f47b64c7d", | |
"bibtex":"9cb70025-a888-4a29-a210-93ec52da40d4", | |
"rdf_dc":"5e3ad958-ac79-463d-812b-a86a9235c28f" | |
}; | |
var unAPIResolver = false; | |
var defaultFormat, unAPIIDs; | |
/** | |
* A class to describe an unAPI format description | |
* @property isSupported {Boolean} Whether Zotero supports a format contained in this description | |
* @property name {String} The unAPI format name, used to retrieve item descriptions | |
* @property translatorID {String} The ID of the translator used to read this format | |
* | |
* @constructor | |
* @param {String} aXML unAPI format description XML | |
*/ | |
UnAPIFormat = function(aXML) { | |
var parser = new DOMParser(); | |
var doc = parser.parseFromString(aXML.replace(/<!DOCTYPE[^>]*>/, "").replace(/<\?xml[^>]*\?>/, ""), "text/xml"); | |
var foundFormat = {}; | |
// Loop through to determine format name | |
var nodes = doc.documentElement.getElementsByTagName("format"); | |
var nNodes = nodes.length; | |
if (!nNodes) { | |
// Hack to fix for Firefox 10/Zotero 3.0.1 | |
var i = 0; | |
while (i in nodes) nNodes = ++i; | |
} | |
var node, name, lowerName, format; | |
for (let i=0; i<nNodes; i++) { | |
node = nodes[i]; | |
name = node.getAttribute("name"); | |
lowerName = name.toLowerCase(); | |
format = false; | |
// Look for formats we can recognize | |
if (["rdf_zotero", "rdf_bibliontology", "bibtex", "endnote", "rdf_dc"].indexOf(lowerName) != -1) { | |
format = lowerName; | |
} else if (lowerName == "rdf_bibliontology") { | |
format = "rdf_bibliontology"; | |
} else if (lowerName === "mods" | |
|| node.getAttribute("namespace_uri") === "http://www.loc.gov/mods/v3" | |
|| node.getAttribute("docs") === "http://www.loc.gov/standards/mods/" | |
|| node.getAttribute("type") === "application/mods+xml") { | |
format = "mods"; | |
} else if (lowerName.match(/^marc\b/) | |
|| node.getAttribute("type") === "application/marc") { | |
format = "marc"; | |
} else if (lowerName.match(/^unimarc\b/) | |
|| node.getAttribute("type") === "application/unimarc") { | |
format = "unimarc"; | |
} else if (lowerName.match(/^marcxml\b/) | |
|| node.getAttribute("type") === "application/marcxml+xml" | |
|| node.getAttribute("docs") === "http://www.loc.gov/marcxml/") { | |
format = "marcxml"; | |
} else if (node.getAttribute("docs") === "http://www.refman.com/support/risformat_intro.asp" | |
|| lowerName.match(/^ris\b/)) { | |
format = "ris"; | |
} | |
if (format) foundFormat[format] = name; | |
} | |
// Loop through again to determine optimal supported format | |
for (let i=0; i<RECOGNIZABLE_FORMATS.length; i++) { | |
if (foundFormat[RECOGNIZABLE_FORMATS[i]]) { | |
this.isSupported = true; | |
this.name = foundFormat[RECOGNIZABLE_FORMATS[i]]; | |
this.translatorID = FORMAT_GUIDS[RECOGNIZABLE_FORMATS[i]]; | |
return; | |
} | |
} | |
this.isSupported = false; | |
}; | |
/** | |
* A class encapsulating an UnAPI ID | |
* @property format {UnAPIFormat} Information regarding the format | |
* @property items {Zotero.Item[]} Items corresponding to this ID | |
* | |
* @constructor | |
* @param {String} id The ID contained in an abbr tag | |
*/ | |
UnAPIID = function(id) { | |
this.id = id; | |
unAPIIDs[id] = this; | |
}; | |
UnAPIID.prototype = { | |
/** | |
* Gets the item type for this item | |
* @param {Function} callback Callback to be passed itemType when it is known | |
*/ | |
"getItemType":function(callback) { | |
var me = this; | |
this.getItems(function(items) { | |
if (items.length === 0) { | |
callback(false); | |
} else if (items.length === 1) { | |
callback(items[0].itemType); | |
} else { | |
callback("multiple"); | |
} | |
}); | |
}, | |
/** | |
* Gets items associated with this ID | |
* @param {Function} callback Callback to be passed items when they have been retrieved | |
*/ | |
"getItems":function(callback) { | |
if (this.items) { | |
callback(this.items); | |
return; | |
} | |
var me = this; | |
this.items = []; | |
this.isSupported(function(isSupported) { | |
if (!isSupported) { | |
callback([]); | |
return; | |
} | |
Zotero.Utilities.HTTP.doGet(unAPIResolver+"?id="+me.id+"&format="+me.format.name, function(text) { | |
var translator = Zotero.loadTranslator("import"); | |
translator.setTranslator(me.format.translatorID); | |
translator.setString(text); | |
translator.setHandler("itemDone", function(obj, item) { | |
// add item to array | |
me.items.push(item); | |
}); | |
translator.setHandler("done", function(obj) { | |
// run callback on item array | |
callback(me.items); | |
}); | |
try { | |
translator.translate(); | |
} catch(e) { | |
Zotero.debug("unAPI: Could not parse "+me.format.name+" metadata for ID "+me.id+": "+e); | |
callback(me.items); | |
} | |
}); | |
}); | |
}, | |
/** | |
* Determines whether Zotero can handle this ID | |
* @param {Function} callback Callback to be passed isSupported when it is known | |
*/ | |
"isSupported":function(callback) { | |
if (this.hasOwnProperty("format")) { | |
callback(this.format.isSupported); | |
return; | |
} | |
var me = this; | |
getDefaultFormat(function(defaultFormat) { | |
// first try default format, since this won't require >1 HTTP request | |
if (defaultFormat.isSupported) { | |
me.format = defaultFormat; | |
callback(true); | |
} else { | |
// if no supported default format, try format for this item | |
Zotero.Utilities.HTTP.doGet(unAPIResolver+"?id="+me.id, function(text) { | |
me.format = new UnAPIFormat(text); | |
callback(!!me.format.isSupported); | |
}); | |
} | |
}); | |
} | |
}; | |
/** | |
* This and the x: prefix in the XPath are to work around an issue with pages | |
* served as application/xhtml+xml | |
* | |
* https://developer.mozilla.org/en/Introduction_to_using_XPath_in_JavaScript#Implementing_a_default_namespace_for_XML_documents | |
*/ | |
function nsResolver() { | |
return 'http://www.w3.org/1999/xhtml'; | |
} | |
/** | |
* Extracts UnAPIIDs from a document | |
* @param {document} A document object from which to extract unAPIIds | |
* @return {UnAPIID[]} The unAPI ID objects extracted from the document | |
*/ | |
function getUnAPIIDs(doc) { | |
// look for a resolver | |
var newUnAPIResolver = doc.evaluate('//x:link[@rel="unapi-server"]', doc, nsResolver, XPathResult.ANY_TYPE, null).iterateNext(); | |
if (!newUnAPIResolver) return []; | |
newUnAPIResolver = newUnAPIResolver.getAttribute("href"); | |
if (unAPIResolver !== newUnAPIResolver) { | |
// if unAPI resolver has changed, clear | |
defaultFormat = false; | |
unAPIResolver = newUnAPIResolver; | |
unAPIIDs = []; | |
} | |
// look for abbrs | |
var abbrs = doc.evaluate('//x:abbr[contains(@class, " unapi-id") or contains(@class, "unapi-id ") or @class="unapi-id"][@title]', | |
doc, nsResolver, XPathResult.ANY_TYPE, null); | |
var abbr; | |
var ids = []; | |
while (abbr = abbrs.iterateNext()) { | |
var id = abbr.getAttribute("title"); | |
ids.push(unAPIIDs[id] ? unAPIIDs[id] : new UnAPIID(id)); | |
} | |
return ids; | |
} | |
/** | |
* Retrieves the list of formats available for all items accessible through this unAPI resolver | |
* @param {Function} callback A callback to be passed the format when it is available | |
*/ | |
function getDefaultFormat(callback) { | |
if (defaultFormat) { | |
callback(defaultFormat); | |
} else { | |
Zotero.Utilities.HTTP.doGet(unAPIResolver, function(text) { | |
// determine format of this item | |
defaultFormat = new UnAPIFormat(text); | |
callback(defaultFormat); | |
}); | |
} | |
} | |
/** | |
* Determines itemType for detection | |
*/ | |
function determineDetectItemType(ids, supportedId) { | |
var id = ids.shift(); | |
id.isSupported(function(isSupported) { | |
if (isSupported && supportedId !== undefined) { | |
// If there are multiple items with valid itemTypes, use "multiple" | |
Zotero.done("multiple"); | |
} else if (ids.length) { | |
// If IDs remain to be handled, handle the next one | |
determineDetectItemType(ids, (isSupported ? id : supportedId)); | |
} else { | |
// If all IDs have been handled, get foundItemType for only supported ID | |
if (isSupported) { | |
id.getItemType(Zotero.done); | |
} else { | |
Zotero.done(false); | |
} | |
} | |
}); | |
} | |
/** | |
* Get all items | |
* @param {UnAPIID[]} ids List of UnAPI IDs | |
* @param {Function} callback Function to pass item array to when items have been retrieved | |
* @param {Zotero.Item[]} items Item array; used for recursive calls | |
**/ | |
function getAllItems(ids, callback, items) { | |
var id = ids.shift(); | |
id.getItems(function(retrievedItems) { | |
var collectedItems = (items ? items.concat(retrievedItems) : retrievedItems); | |
if (ids.length) { | |
getAllItems(ids, callback, collectedItems); | |
} else { | |
callback(collectedItems); | |
} | |
}); | |
} | |
function detectWeb(doc, url) { | |
// get unAPI IDs | |
var ids = getUnAPIIDs(doc); | |
if (!ids.length) return false; | |
if (!ids.length === 1) { | |
// Only one item, so we will just get its item type | |
ids[0].getItemType(Zotero.done); | |
} else { | |
// Several items. We will need to call determineDetectItemType | |
determineDetectItemType(ids); | |
} | |
} | |
function doWeb(doc, url) { | |
var ids = getUnAPIIDs(doc); | |
getAllItems(ids, function(items) { | |
// get the domain we're scraping, so we can use it for libraryCatalog | |
domain = doc.location.href.match(/https?:\/\/([^/]+)/); | |
if (items.length == 1) { | |
// If only one item, just complete it | |
items[0].libraryCatalog = domain[1]; | |
items[0].complete(); | |
} else if (items.length > 0) { | |
// If multiple items, extract their titles | |
var itemTitles = {}; | |
for (var i in items) { | |
itemTitles[i] = items[i].title; | |
} | |
// Show item selection dialog | |
Zotero.selectItems(itemTitles, function(chosenItems) { | |
if (!chosenItems) return true; | |
// Complete items | |
for (var i in chosenItems) { | |
items[i].libraryCatalog = domain[1]; | |
items[i].complete(); | |
} | |
}); | |
} | |
}); | |
} | |
/** BEGIN TEST CASES **/ | |
var testCases = [ | |
{ | |
"type": "web", | |
"url": "https://search.library.utoronto.ca/search?N=0&Ntk=Anywhere&Ntt=nimni+challenge+of+post-zionism&Ntx=mode%252Bmatchallpartial&Nu=p_work_normalized&Np=1&formName=search_form_simple", | |
"items": [ | |
{ | |
"itemType": "book", | |
"title": "The challenge of Post-Zionism: alternatives to Israeli fundamentalist politics", | |
"creators": [ | |
{ | |
"firstName": "Ephraim", | |
"lastName": "Nimni", | |
"creatorType": "editor" | |
} | |
], | |
"date": "2003", | |
"ISBN": "9781856498937 9781856498944", | |
"callNumber": "DS113.4 .C45 2003", | |
"extra": "OCLC: 50670646", | |
"libraryCatalog": "search.library.utoronto.ca", | |
"numPages": "209", | |
"place": "London ; New York", | |
"publisher": "Zed Books", | |
"series": "Postcolonial encounters", | |
"shortTitle": "The challenge of Post-Zionism", | |
"attachments": [], | |
"tags": [ | |
{ | |
"tag": "Israel" | |
}, | |
{ | |
"tag": "National characteristics, Israeli" | |
}, | |
{ | |
"tag": "Philosophy" | |
}, | |
{ | |
"tag": "Politics and government" | |
}, | |
{ | |
"tag": "Post-Zionism" | |
}, | |
{ | |
"tag": "Zionism" | |
} | |
], | |
"notes": [], | |
"seeAlso": [] | |
} | |
] | |
}, | |
{ | |
"type": "web", | |
"url": "https://search.library.utoronto.ca/search?N=0&Ntk=Anywhere&Ntt=adam+smith&Ntx=mode%252Bmatchallpartial&Nu=p_work_normalized&Np=1&formName=search_form_simple", | |
"items": "multiple" | |
}, | |
{ | |
"type": "web", | |
"url": "http://demo1.orex.es/cgi-bin/koha/opac-detail.pl?biblionumber=3", | |
"items": [ | |
{ | |
"itemType": "book", | |
"title": "Carlota Fainberg", | |
"creators": [ | |
{ | |
"firstName": "Antonio", | |
"lastName": "Muñoz Molina", | |
"creatorType": "author" | |
} | |
], | |
"date": "1999", | |
"ISBN": "9788420441610", | |
"libraryCatalog": "demo1.orex.es", | |
"numPages": "174", | |
"place": "Madrid", | |
"publisher": "Alfaguara", | |
"attachments": [], | |
"tags": [], | |
"notes": [], | |
"seeAlso": [] | |
} | |
] | |
} | |
] | |
/** END TEST CASES **/ |