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
Fetching contributors…
Cannot retrieve contributors at this time
{ | |
"translatorID": "47533cd7-ccaa-47a7-81bb-71c45e68a74d", | |
"label": "Bibliothèque nationale de France", | |
"creator": "Florian Ziche, Sylvain Machefert", | |
"target": "^https?://[^/]*catalogue\\.bnf\\.fr", | |
"minVersion": "3.0", | |
"maxVersion": "", | |
"priority": 100, | |
"inRepository": true, | |
"translatorType": 4, | |
"browserSupport": "g", | |
"lastUpdated": "2017-07-07 11:47:44" | |
} | |
/* | |
* Bibliothèque nationale de France Translator | |
* Copyright (C) 2010 Florian Ziche, ziche@noos.fr | |
* | |
* This program is free software: you can redistribute it and/or modify | |
* it under the terms of the GNU General Public License as published by | |
* the Free Software Foundation, either version 3 of the License, or | |
* (at your option) any later version. | |
* | |
* This program 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 General Public License for more details. | |
* | |
* You should have received a copy of the GNU General Public License | |
* along with this program. If not, see <http://www.gnu.org/licenses/>. | |
*/ | |
/* Bnf namespace. */ | |
var BnfClass = function() { | |
//Private members | |
/* Map MARC responsibility roles to Zotero creator types. | |
See http://archive.ifla.org/VI/3/p1996-1/appx-c.htm. | |
*/ | |
function getCreatorType(aut) { | |
var typeAut = aut['4'].trim(); | |
switch (typeAut) { | |
case "005": | |
case "250": | |
case "275": | |
case "590": //performer | |
case "755": //vocalist | |
return "performer"; | |
case "040": | |
case "130": //book designer | |
case "740": //type designer | |
case "750": //typographer | |
case "350": //engraver | |
case "360": //etcher | |
case "430": //illuminator | |
case "440": //illustrator | |
case "510": //lithographer | |
case "530": //metal engraver | |
case "600": //photographer | |
case "705": //sculptor | |
case "760": //wood engraver | |
return "artist"; | |
case "070": | |
case "305": | |
case "330": | |
case undefined: | |
return "author"; | |
case "020": | |
case "210": | |
case "212": | |
return "commenter"; | |
case "180": | |
return "cartographer"; | |
case "220": | |
case "340": | |
return "editor"; | |
case "230": | |
return "composer"; | |
case "245": | |
return "inventor"; | |
case "255": | |
case "695": //scientific advisor | |
case "727": //thesis advisor | |
return "counsel"; | |
case "300": | |
return "director"; | |
case "400": //funder | |
case "723": //sponsor | |
return "sponsor"; | |
case "460": | |
return "interviewee"; | |
case "470": | |
return "interviewer"; | |
case "480": //librettist | |
case "520": //lyricist | |
return "wordsBy"; | |
case "605": | |
return "presenter"; | |
case "630": | |
return "producer"; | |
case "635": | |
return "programmer"; | |
case "660": | |
return "recipient"; | |
case "090": //author of dialog | |
case "690": //scenarist | |
return "scriptwriter"; | |
case "730": | |
return "translator"; | |
//Ignore (no matching Zotero creatorType): | |
case "320": //donor | |
case "610": //printer | |
case "650": //publisher | |
return undefined; | |
//Default | |
case "205": | |
default: | |
return "contributor"; | |
} | |
}; | |
/* Fix creators (MARC translator is not perfect). */ | |
function getCreators(record, item) { | |
//Clear creators | |
item.creators = new Array(); | |
// Extract creators (700, 701 & 702) | |
for (var i = 700; i < 703; i++) { | |
var authorTag = record.getFieldSubfields(i); | |
for (var j in authorTag) { | |
var aut = authorTag[j]; | |
var authorText = ""; | |
if (aut.b) { | |
authorText = aut['a'] + ", " + aut['b']; | |
} else { | |
authorText = aut['a']; | |
} | |
var type = getCreatorType(aut); | |
if (type) { | |
item.creators.push(Zotero.Utilities.cleanAuthor(authorText, type, true)); | |
} | |
} | |
} | |
// Extract corporate creators (710, 711 & 712) | |
for (var i = 710; i < 713; i++) { | |
var authorTag = record.getFieldSubfields(i); | |
for (var j in authorTag) { | |
if (authorTag[j]['a']) { | |
var type = getCreatorType(authorTag[j]); | |
if (type) { | |
item.creators.push({ | |
lastName: authorTag[j]['a'], | |
creatorType: type, | |
fieldMode: true | |
}); | |
} | |
} | |
} | |
} | |
}; | |
//Add tag, if not present yet | |
function addTag(item, tag) { | |
for (var t in item.tags) { | |
if (item.tags[t] == tag) { | |
return; | |
} | |
} | |
item.tags.push(tag); | |
}; | |
//Tagging | |
function getTags(record, item) { | |
var pTag = record.getFieldSubfields("600"); | |
if (pTag) { | |
for (var j in pTag) { | |
var tagText = false; | |
var person = pTag[j]; | |
tagText = person.a; | |
if (person.b) { | |
tagText += ", " + person.b; | |
} | |
if (person.c) { | |
tagText += ", " + person.c; | |
} | |
if (person.f) { | |
tagText += " (" + person.f + ")"; | |
} | |
addTag(item, tagText); | |
} | |
} | |
pTag = record.getFieldSubfields("601"); | |
if (pTag) { | |
for (var j in pTag) { | |
var tagText = false; | |
var person = pTag[j]; | |
tagText = person.a; | |
addTag(item, tagText); | |
} | |
} | |
pTag = record.getFieldSubfields("605"); | |
if (pTag) { | |
for (var j in pTag) { | |
var tagText = false; | |
var person = pTag[j]; | |
tagText = person.a; | |
addTag(item, tagText); | |
} | |
} | |
pTag = record.getFieldSubfields("606"); | |
if (pTag) { | |
for (var j in pTag) { | |
var tagText = false; | |
var person = pTag[j]; | |
tagText = person.a; | |
addTag(item, tagText); | |
} | |
} | |
pTag = record.getFieldSubfields("607"); | |
if (pTag) { | |
for (var j in pTag) { | |
var tagText = false; | |
var person = pTag[j]; | |
tagText = person.a; | |
addTag(item, tagText); | |
} | |
} | |
pTag = record.getFieldSubfields("602"); | |
if (pTag) { | |
for (var j in pTag) { | |
var tagText = false; | |
var person = pTag[j]; | |
tagText = person.a; | |
if (person.f) { | |
tagText += " (" + person.f + ")"; | |
} | |
addTag(item, tagText); | |
} | |
} | |
pTag = record.getFieldSubfields("604"); | |
if (pTag) { | |
for (var j in pTag) { | |
var tagText = false; | |
var person = pTag[j]; | |
tagText = person.a; | |
if (person.b) { | |
tagText += ", " + person.b; | |
} | |
if (person.f) { | |
tagText += " (" + person.f + ")"; | |
} | |
if (person.t) { | |
tagText += ", " + person.t; | |
} | |
addTag(item, tagText); | |
} | |
} | |
}; | |
//Get series (repeatable) | |
function getSeries(record, item) { | |
var seriesText = false; | |
var seriesTag = record.getFieldSubfields("225"); | |
if (seriesTag && seriesTag.length > 1) { | |
for (var j in seriesTag) { | |
var series = seriesTag[j]; | |
if (seriesText) { | |
seriesText += "; "; | |
} else { | |
seriesText = ""; | |
} | |
seriesText += series.a; | |
if (series.v) { | |
seriesText += ", " + series.v; | |
} | |
} | |
if (seriesText) { | |
delete item.seriesNumber; | |
item.series = seriesText; | |
} | |
} | |
//Try 461 | |
if (!item.series) { | |
seriesTag = record.getFieldSubfields("461"); | |
if (seriesTag) { | |
for (var j in seriesTag) { | |
var series = seriesTag[j]; | |
if (seriesText) { | |
seriesText += "; "; | |
} else { | |
seriesText = ""; | |
} | |
seriesText += series.t; | |
} | |
} | |
if (seriesText) { | |
delete item.seriesNumber; | |
item.series = seriesText; | |
} | |
} | |
}; | |
//Add extra text | |
function addExtra(noteText, extra) { | |
if (extra) { | |
if (noteText) { | |
if (!/\.$/.exec(noteText)) { | |
noteText += ". "; | |
} else { | |
noteText += " "; | |
} | |
} else { | |
noteText = ""; | |
} | |
noteText += Zotero.Utilities.trim(extra); | |
} | |
return noteText; | |
} | |
//Assemble extra information | |
function getExtra(record, item) { | |
var noteText = false; | |
//Material description | |
var noteTag = record.getFieldSubfields("215"); | |
if (noteTag) { | |
for (var j in noteTag) { | |
var note = noteTag[j]; | |
noteText = addExtra(noteText, note.c); | |
noteText = addExtra(noteText, note.d); | |
noteText = addExtra(noteText, note.e); | |
} | |
} | |
//Note | |
noteTag = record.getFieldSubfields("300"); | |
if (noteTag) { | |
for (var j in noteTag) { | |
var note = noteTag[j]; | |
noteText = addExtra(noteText, note.a); | |
} | |
} | |
//Edition history notes | |
noteTag = record.getFieldSubfields("305"); | |
if (noteTag) { | |
for (var j in noteTag) { | |
var note = noteTag[j]; | |
noteText = addExtra(noteText, note.a); | |
} | |
} | |
if (noteText) { | |
if (!/\.$/.exec(noteText)) { | |
noteText += "."; | |
} | |
item.extra = noteText; | |
} | |
}; | |
//Get title from 200 | |
function getTitle(record, item) { | |
var titleTag = record.getFieldSubfields("200"); | |
if (titleTag) { | |
titleTag = titleTag[0]; | |
var titleText = titleTag.a; | |
if (titleTag.e) { | |
if (!/^[,\.:;-]/.exec(titleTag.e)) { | |
titleText += ": "; | |
} | |
titleText += titleTag.e; | |
} | |
if (titleTag.h) { | |
titleText += ", " + titleTag.h; | |
if (titleTag.i) { | |
titleText += ": " + titleTag.i; | |
} | |
} else if (titleTag.i) { | |
titleText += ", " + titleTag.i; | |
} | |
item.title = titleText; | |
} | |
}; | |
function getCote(record, item) { | |
item.callNumber = ""; | |
var coteTag = record.getFieldSubfields("930"); | |
if (coteTag.length) { | |
item.callNumber += coteTag[0]['c'] + "-" + coteTag[0]['a']; | |
} | |
}; | |
//Do BnF specific Unimarc postprocessing | |
function postprocessMarc(record, newItem) { | |
//Title | |
getTitle(record, newItem); | |
//Fix creators | |
getCreators(record, newItem); | |
//Fix callNumber | |
getCote(record, newItem); | |
//Store perennial url from 003 as attachment and accession number | |
var url = record.getField("003"); | |
if (url && url.length > 0 && url[0][1]) { | |
newItem.attachments.push({ | |
title: 'Lien vers la notice du catalogue', | |
url: url[0][1], | |
mimeType: 'text/html', | |
snapshot: false | |
}); | |
} | |
//Country (102a) | |
record._associateDBField(newItem, "102", "a", "country"); | |
//Try to retrieve volumes/pages from 215d | |
if (!newItem.pages) { | |
var dimTag = record.getFieldSubfields("215"); | |
for (var j in dimTag) { | |
var dim = dimTag[j]; | |
if (dim.a) { | |
var pages = /[^\d]*(\d+)\s+p\..*/.exec(dim.a); | |
if (pages) { | |
newItem.numPages = pages[1]; | |
} | |
var vols = /[^\d]*(\d+)\s+vol\..*/.exec(dim.a); | |
if (vols) { | |
newItem.numberOfVolumes = vols[1]; | |
} | |
} | |
} | |
} | |
//Series | |
getSeries(record, newItem); | |
//Extra | |
getExtra(record, newItem); | |
//Tagging | |
getTags(record, newItem); | |
//Repository | |
newItem.libraryCatalog = "BnF Catalogue général (http://catalogue.bnf.fr)"; | |
}; | |
//Public members | |
/* Get the UNIMARC URL for a given single result page. */ | |
this.reformURL = function(url) { | |
url = url.replace(/(^.*\/ark:\/12148\/cb[0-9]+[a-z]*)(.*$)/, "$1.unimarc"); | |
// Zotero.debug("URL1 "+ url); | |
return url; | |
}; | |
/* Get the results table from a list page, if any. Looks for //table[@class="ListeNotice"]. */ | |
this.getResultsTable = function(doc) { | |
try { | |
var xPath = '//div[@class="liste-notices"]'; | |
var xPathObject = doc.evaluate(xPath, doc, null, XPathResult.ANY_TYPE, null).iterateNext(); | |
return xPathObject; | |
} catch (x) { | |
Zotero.debug(x.lineNumber + " " + x.message); | |
} | |
return undefined; | |
}; | |
/* Get selectable search items from a list page. | |
Loops through //td[@class="mn_partienoticesynthetique"], extracting the single items URLs from | |
their onclick attribute, thier titles by assembling the spans for each cell. | |
*/ | |
this.getSelectedItems = function(doc) { | |
var items = new Object(); | |
var cellPath = '//div[@class="liste-notices"]/div[@class="notice-item"]'; | |
var cells = doc.evaluate(cellPath, doc, null, XPathResult.ANY_TYPE, null); | |
while (cell = cells.iterateNext()) { | |
var link = doc.evaluate('./div[@class="notice-contenu"]/div[@class="notice-synthese"]/a', cell, null, XPathResult.ANY_TYPE, null).iterateNext(); | |
var title = doc.evaluate('./h2', link, null, XPathResult.ANY_TYPE, null).iterateNext(); | |
if (title) { | |
title = ZU.trim(title.textContent); | |
} else { | |
// 2016-01-26 : Sometimes there is no H2, we then need to get everything, example when searching for : | |
// "Se souvenir de Tonnay-Charente" | |
title = ZU.trim(link.textContent); | |
} | |
var documentYear = doc.evaluate('./div[@class="notice-ordre"]', cell, null, XPathResult.ANY_TYPE, null).iterateNext(); | |
if (documentYear) { | |
title += " / " + documentYear.textContent; | |
} | |
var url = link.href; | |
items[url] = title; | |
} | |
return items; | |
}; | |
//Check for Gallica URL (digital version available), if found, set item.url | |
function checkGallica(record, item) { | |
var url = record.getFieldSubfields("856"); | |
if (url && url.length > 0 && url[0].u) { | |
item.url = url[0].u; | |
} | |
} | |
/* Process UNIMARC URL. */ | |
this.processMarcUrl = function(newDoc, url) { | |
/* Init MARC record. */ | |
// Load MARC | |
var translator = Zotero.loadTranslator("import"); | |
translator.setTranslator("a6ee60df-1ddc-4aae-bb25-45e0537be973"); | |
translator.getTranslatorObject(function(obj) { | |
var record = new obj.record(); | |
/* Get table cell containing MARC code. */ | |
var elmts = newDoc.evaluate('//div[@class="notice-detail"]/div/div[@class="zone"]', | |
newDoc, null, XPathResult.ANY_TYPE, null); | |
/* Line loop. */ | |
var elmt, tag, content; | |
var ind = ""; | |
while (elmt = elmts.iterateNext()) { | |
var line = Zotero.Utilities.superCleanString(elmt.textContent); | |
if (line.length == 0) { | |
continue; | |
} | |
if (line.substring(0, 6) == " ") { | |
content += " " + line.substring(6); | |
continue; | |
} else { | |
if (tag) { | |
record.addField(tag, ind, content); | |
} | |
} | |
line = line.replace(/[_\t\xA0]/g, " "); // nbsp | |
tag = line.substr(0, 3); | |
if (tag[0] != "0" || tag[1] != "0") { | |
ind = line.substr(3, 2); | |
content = line.substr(5).replace(/\$([a-z]|[0-9])/g, obj.subfieldDelimiter + "$1"); | |
content = content.replace(/ˆ([^‰]+)‰/g, "$1"); | |
} else { | |
if (tag == "000") { | |
tag = undefined; | |
record.leader = "0000" + line.substr(8); | |
} else { | |
content = line.substr(3); | |
} | |
} | |
} | |
// case last zone | |
if (tag) { | |
record.addField(tag, ind, content); | |
} | |
//Create item | |
var newItem = new Zotero.Item(); | |
record.translate(newItem); | |
//Do specific Unimarc postprocessing | |
postprocessMarc(record, newItem); | |
//Check for Gallica URL | |
checkGallica(record, newItem); | |
//We have to restore the public view for the next actions | |
//by the users, which is achieved by opening the url with | |
//public ending again. | |
if (url.indexOf('.public')==-1) { | |
url = url.replace('.unimarc', '') + '.public'; | |
} | |
ZU.doGet(url, function(text) {; | |
newItem.complete(); | |
}); | |
}); | |
}; | |
}; | |
/* Global BnfClass object. */ | |
var Bnf = new BnfClass(); | |
/* Translator API implementation. */ | |
function detectWeb(doc, url) { | |
var resultRegexp = /ark:\/12148\/cb[0-9]+/i; | |
//Single result ? | |
if (resultRegexp.test(url)) { | |
return "single"; | |
} | |
//Muliple result ? | |
else if (Bnf.getResultsTable(doc)) { | |
return "multiple"; | |
} | |
//No items | |
return undefined; | |
} | |
function doWeb(doc, url) { | |
/* Check type. */ | |
var type = detectWeb(doc, url); | |
Zotero.debug("type " + type); | |
if (!type) { | |
return; | |
} | |
/* Build array of MARC URLs. */ | |
var urls = new Array(); | |
switch (type) { | |
case "multiple": | |
var items = Bnf.getSelectedItems(doc); | |
if (!items) { | |
return true; | |
} | |
/* Let user select items. */ | |
Zotero.selectItems(items, function(items) { | |
for (var i in items) { | |
urls.push(Bnf.reformURL(i)); | |
} | |
if (urls.length > 0) { | |
//Z.debug(urls) | |
Zotero.Utilities.processDocuments(urls, function(doc) { | |
Bnf.processMarcUrl.call(Bnf, doc, urls[0]); | |
}); | |
} | |
}); | |
break; | |
case "single": | |
urls = [Bnf.reformURL(url)]; | |
Zotero.Utilities.processDocuments(urls, function(doc) { | |
Bnf.processMarcUrl.call(Bnf, doc, url); | |
}); | |
break; | |
default: | |
// nothing to do | |
break; | |
} | |
} | |
/** BEGIN TEST CASES **/ | |
var testCases = [] | |
/** END TEST CASES **/ |