www

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | Submodules | README | LICENSE

commit 1d4c2715f127c3da33bd495d6358aa560874361e
parent 665e1ec826de9d41eb01763dab8535d77906df72
Author: Simon Kornblith <simon@simonster.com>
Date:   Sun, 21 Mar 2010 06:13:28 +0000

draft Bibliontology translator


Diffstat:
Atranslators/Bibliontology RDF.js | 995+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 995 insertions(+), 0 deletions(-)

diff --git a/translators/Bibliontology RDF.js b/translators/Bibliontology RDF.js @@ -0,0 +1,994 @@ +{ + "translatorID":"14763d25-8ba0-45df-8f52-b8d1108e7ac9", + "translatorType":3, + "label":"Bibliontology RDF", + "creator":"Simon Kornblith", + "target":"rdf", + "minVersion":"2.0", + "maxVersion":"", + "priority":200, + "inRepository":true, + "lastUpdated":"2009-06-29 22:16:41" +} + +Zotero.configure("getCollections", true); +Zotero.configure("dataMode", "rdf"); +Zotero.addOption("exportNotes", true); +Zotero.addOption("exportFileData", false); + +var n = { + address:"http://schemas.talis.com/2005/address/schema#", // could also use vcard? + bibo:"http://purl.org/ontology/biblio/", + dcterms:"http://purl.org/dc/terms/", + doap:"http://usefulinc.com/ns/doap#", + foaf:"http://xmlns.com/foaf/0.1/", + link:"http://purl.org/rss/1.0/modules/link/", + po:"http://purl.org/ontology/po/", + rdf:"http://www.w3.org/1999/02/22-rdf-syntax-ns#", + rel:"http://www.loc.gov/loc.terms/relators/", + res:"http://purl.org/vocab/resourcelist/schema#", + sc:"http://umbel.org/umbel/sc/", + sioct:"http://rdfs.org/sioc/types#", + z:"http://www.zotero.org/namespaces/export#" +}; + +/** + Types should be in the form + + <ZOTERO_TYPE>: [<ITEM_CLASS>, <SUBCONTAINER_CLASS>, <CONTAINER_CLASS>] + + Item classes should be in the form + + [[<PREDICATE>, <OBJECT>]+] + + This generates the triples + + (ITEM <PREDICATE> <OBJECT>)+ + + Subcontainer and container classes should be in the form + + [<ALWAYS_INCLUDE>, <ITEM_PREDICATE>, [<CONTAINER_PREDICATE>, <CONTAINER_OBJECT>]*] | null + + If there is a property to be applied to the container, or if <ALWAYS_INCLUDE> is true, then this + generates + + ITEM <ITEM_PREDICATE> CONTAINER + (CONTAINER <CONTAINER_PREDICATE> <CONTAINER_OBJECT>)* + **/ + +// ZOTERO TYPE ITEM CLASS SUBCONTAINER CLASS CONTAINER CLASS +var TYPES = { + "artwork": [[[n.rdf+"type", n.bibo+"Image"]], null, null], + "attachment": [[[n.rdf+"type", n.z+"Attachment"]], null, null], + "audioRecording": [[[n.rdf+"type", n.bibo+"AudioDocument"]], null, null], + "bill": [[[n.rdf+"type", n.bibo+"Bill"]], null, [false, n.dcterms+"isPartOf", [[n.rdf+"type", n.bibo+"Code"]]]], + "blogPost": [[[n.rdf+"type", n.sioct+"BlogPost"], + [n.rdf+"type", n.bibo+"Article"]], null, [false, n.dcterms+"isPartOf", [[n.rdf+"type", n.sioct+"Weblog"], + [n.rdf+"type", n.bibo+"Website"]]]], + "book": [[[n.rdf+"type", n.bibo+"Book"]], null, null], + "bookSection": [[[n.rdf+"type", n.bibo+"BookSection"]], null, [false, n.dcterms+"isPartOf", [[n.rdf+"type", n.bibo+"EditedBook"]]]], + "case": [[[n.rdf+"type", n.bibo+"LegalCaseDocument"]], null, [false, n.dcterms+"isPartOf", [[n.rdf+"type", n.bibo+"CourtReporter"]]]], + "computerProgram": [[[n.rdf+"type", n.sc+"ComputerProgram_CW"], + [n.rdf+"type", n.bibo+"Document"]], null, null], + "conferencePaper": [[[n.rdf+"type", n.bibo+"Article"]], null, [true, n.dcterms+"isPartOf", [[n.rdf+"type", n.bibo+"Proceedings"]]]], + "dictionaryEntry": [[[n.rdf+"type", n.bibo+"Article"]], null, [true, n.dcterms+"isPartOf", [[n.rdf+"type", n.sc+"Dictionary"], + [n.rdf+"type", n.bibo+"ReferenceSource"]]]], + "document": [[[n.rdf+"type", n.bibo+"Document"]], null, null], + "email": [[[n.rdf+"type", n.bibo+"Email"]], null, null], + "encyclopediaArticle": [[[n.rdf+"type", n.bibo+"Article"]], null, [true, n.dcterms+"isPartOf", [[n.rdf+"type", n.sc+"Encyclopedia"], + [n.rdf+"type", n.bibo+"ReferenceSource"]]]], + "forumPost": [[[n.rdf+"type", n.sioct+"BoardPost"], + [n.rdf+"type", n.bibo+"Article"]], null, [false, n.dcterms+"isPartOf", [[n.rdf+"type", n.sioct+"MessageBoard"], + [n.rdf+"type", n.bibo+"Website"]]]], + "film": [[[n.rdf+"type", n.bibo+"Film"]], null, null], + "hearing": [[[n.rdf+"type", n.bibo+"Hearing"]], null, null], + "instantMessage": [[[n.rdf+"type", n.sioct+"InstantMessage"], + [n.rdf+"type", n.bibo+"PersonalCommunication"]], null, null], + "interview": [[[n.rdf+"type", n.bibo+"Interview"]], null, null], + "journalArticle": [[[n.rdf+"type", n.bibo+"AcademicArticle"]], [true, n.dcterms+"isPartOf", + [[n.rdf+"type", n.bibo+"Issue"]]], [true, n.dcterms+"isPartOf", [[n.rdf+"type", n.bibo+"Journal"]]]], + "letter": [[[n.rdf+"type", n.bibo+"Letter"]], null, null], + "magazineArticle": [[[n.rdf+"type", n.bibo+"Article"]], [true, n.dcterms+"isPartOf", + [[n.rdf+"type", n.bibo+"Issue"]]], [true, n.dcterms+"isPartOf", [[n.rdf+"type", n.bibo+"Magazine"]]]], + "manuscript": [[[n.rdf+"type", n.bibo+"Manuscript"]], null, null], + "map": [[[n.rdf+"type", n.bibo+"Map"]], null, null], + "newspaperArticle": [[[n.rdf+"type", n.bibo+"Article"]], [true, n.dcterms+"isPartOf", + [[n.rdf+"type", n.bibo+"Issue"]]], [true, n.dcterms+"isPartOf", [[n.rdf+"type", n.bibo+"Newspaper"]]]], + "note": [[[n.rdf+"type", n.bibo+"Note"]], null, null], + "patent": [[[n.rdf+"type", n.bibo+"Patent"]], null, null], + "podcast": [[[n.rdf+"type", n.z+"Podcast"], + [n.rdf+"type", n.bibo+"AudioDocument"]], null, null], + "presentation": [[[n.rdf+"type", n.bibo+"Slideshow"]], null, null], + "radioBroadcast": [[[n.rdf+"type", n.po+"AudioDocument"], + [n.rdf+"type", n.po+"Episode"], + [n.po+"broadcast_on", n.po+"Radio"]], null, [n.rdf+"type", n.po+"Programme"]], + "report": [[[n.rdf+"type", n.bibo+"Report"]], null, null], + "statute": [[[n.rdf+"type", n.bibo+"Statute"]], null, [false, n.dcterms+"isPartOf", [[n.rdf+"type", n.bibo+"Code"]]]], + "thesis": [[[n.rdf+"type", n.bibo+"Thesis"]], null, null], + "tvBroadcast": [[[n.rdf+"type", n.bibo+"AudioVisualDocument"], + [n.rdf+"type", n.po+"Episode"], + [n.po+"broadcast_on", n.po+"TV"]], null, [n.rdf+"type", n.po+"Programme"]], + "videoRecording": [[[n.rdf+"type", n.bibo+"AudioVisualDocument"]], null, null], + "webpage": [[[n.rdf+"type", n.bibo+"Webpage"]], null, [false, n.dcterms+"isPartOf", [[n.rdf+"type", n.bibo+"Website"]]]] +}; + +/** + * This is just a map of un-namespaced BIBO item types to Zotero item types + */ +var BIBO_TYPES = { + "Article": "magazineArticle", + "Brief": "case", + "Chapter": "bookSection", + "CollectedDocument": "document", + "DocumentPart": "document", + "EditedBook": "book", + "Excerpt": "note", + "Quote": "note", + "Film": "videoRecording", + "LegalDecision": "case", + "LegalDocument": "case", + "Legislation": "bill", + "Manual": "book", + "Performance": "presentation", + "PersonalCommunication": "letter", + "PersonalCommunicationDocument": "letter", + "Slide": "presentation", + "Standard": "report", + "Website": "webpage" +}; + +var USERITEM = 1; +var ITEM = 2; +var SUBCONTAINER = 3; +var CONTAINER = 4; +var ITEM_SERIES = 5; +var SUBCONTAINER_SERIES = 6; // not used +var CONTAINER_SERIES = 7; + +/** + Fields should be in the form + + <ZOTERO_FIELD>: ([<SUBJECT>, <PREDICATE>] | <FUNCTION>) + + If a <FUNCTION> is specified, then it is passed the item and should return a set of triples in + the form + + [[<SUBJECT>, <PREDICATE>, <OBJECT>, <LITERAL>]*] + + where <SUBJECT> refers to one of the constants defined above. If <LITERAL> is true, then + <OBJECT> is treated as a literal. + + If a <FUNCTION> is not used and <PREDICATE> is a string, then the parameters generate a triple + in the form + + <SUBJECT> <PREDICATE> FIELD_CONTENT + + where <SUBJECT> refers to one of the constants defined above. Alternatively, <PREDICATE> may be + an array in the form + + [<ITEM_PREDICATE>, [<BLANK_NODE_PREDICATE>, <BLANK_NODE_OBJECT>]*, <PREDICATE>] + + This generates the triples + + <SUBJECT> <ITEM_PREDICATE> <BLANK_NODE> + (<BLANK_NODE> <BLANK_NODE_PREDICATE> <BLANK_NODE_OBJECT>)* + <BLANK_NODE> <PREDICATE> FIELD_CONTENT +**/ +var FIELDS = { + "url": [ITEM, n.bibo+"uri"], + "rights": [USERITEM, n.dcterms+"rights"], + "series": [CONTAINER_SERIES, n.dcterms+"title"], + "volume": [SUBCONTAINER, n.bibo+"volume"], + "issue": [SUBCONTAINER, n.bibo+"issue"], + "edition": [SUBCONTAINER, n.bibo+"edition"], + "place": [CONTAINER, [n.dcterms+"publisher", [[n.rdf+"type", n.foaf+"Organization"]], n.address+"localityName"]], + "country": [CONTAINER, [n.dcterms+"publisher", [[n.rdf+"type", n.foaf+"Organization"]], n.address+"countryName"]], + "publisher": [CONTAINER, [n.dcterms+"publisher", [[n.rdf+"type", n.foaf+"Organization"]], n.foaf+"name"]], + "pages": [ITEM, n.bibo+"pages"], + "firstPage": [ITEM, n.bibo+"pageStart"], + "ISBN": [function(item) { + var isbns = item.ISBN.split(/, ?| /g); + var triples = []; + for each(var isbn in isbns) { + if(isbn.length == 10) { + triples.push([CONTAINER, n.bibo+"isbn10", isbn, true]); + } else { + triples.push([CONTAINER, n.bibo+"isbn13", isbn, true]); + } + } + return triples; + }, function(nodes) { + var isbns = []; + for each(var prop in [n.bibo+"isbn13", n.bibo+"isbn10"]) { + var statements = Zotero.RDF.getStatementsMatching(nodes[CONTAINER], prop, null); + if(statements) { + for each(var statement in statements) { + isbns.push(statement[2]); + } + } + } + if(!isbns.length) return false; + return isbns.join(", "); + }], + "publicationTitle": [CONTAINER, n.dcterms+"title"], + "ISSN": [CONTAINER, n.bibo+"issn"], + "date": [SUBCONTAINER, n.dcterms+"date"], + "section": [ITEM, n.bibo+"section"], + "callNumber": [SUBCONTAINER, n.bibo+"lccn"], + "archiveLocation": [ITEM, n.dcterms+"source"], + "distributor": [SUBCONTAINER, n.bibo+"distributor"], + "extra": [ITEM, n.z+"extra"], + "journalAbbreviation": [CONTAINER, n.bibo+"shortTitle"], + "DOI": [ITEM, n.bibo+"doi"], + "accessDate": [USERITEM, n.z+"accessDate"], + "seriesTitle": [ITEM_SERIES, n.dcterms+"title"], + "seriesText": [ITEM_SERIES, n.dcterms+"description"], + "seriesNumber": [CONTAINER_SERIES, n.bibo+"number"], + "code": [CONTAINER, n.dcterms+"title"], + "session": [ITEM, [n.bibo+"presentedAt", [[n.rdf+"type", n.bibo+"Conference"]], n.dcterms+"title"]], + "legislativeBody": [ITEM, [n.bibo+"organizer", [[n.rdf+"type", n.sc+"LegalGovernmentOrganization"], [n.rdf+"type", n.foaf+"Organization"]], n.foaf+"name"]], + "history": [ITEM, n.z+"history"], + "reporter": [CONTAINER, n.dcterms+"title"], + "court": [CONTAINER, n.bibo+"court"], + "numberOfVolumes": [CONTAINER_SERIES, n.bibo+"numberOfVolumes"], + "committee": [ITEM, [n.bibo+"organizer", [[n.rdf+"type", n.sc+"Committee_Organization"], [n.rdf+"type", n.foaf+"Organization"]], n.foaf+"name"]], + "assignee": [ITEM, n.z+"assignee"], // TODO + "priorityNumbers": [function(item) { // TODO + var priorityNumbers = item.priorityNumbers.split(/, ?| /g); + return [[ITEM, n.z+"priorityNumber", number, true] for each(number in priorityNumbers)]; + }, function(nodes) { + var statements = Zotero.RDF.getStatementsMatching(nodes[ITEM], n.z+"priorityNumber", null); + if(!statements) return false; + return [statement[2] for each(statement in statements)].join(", "); + }], + "references": [ITEM, n.z+"references"], + "legalStatus": [ITEM, n.bibo+"status"], + "codeNumber": [CONTAINER, n.bibo+"number"], + "number": [ITEM, n.bibo+"number"], + "artworkSize": [ITEM, n.dcterms+"extent"], + "libraryCatalog": [USERITEM, n.z+"repository"], + "archive": [ITEM, n.z+"repository"], + "scale": [ITEM, n.z+"scale"], + "meetingName": [ITEM, [n.bibo+"presentedAt", [[n.rdf+"type", n.bibo+"Conference"]], n.dcterms+"title"]], + "runningTime": [ITEM, n.po+"duration"], + "version": [ITEM, n.doap+"revision"], + "system": [ITEM, n.doap+"os"], + "conferenceName": [ITEM, [n.bibo+"presentedAt", [[n.rdf+"type", n.bibo+"Conference"]], n.dcterms+"title"]], + "language": [ITEM, n.dcterms+"language"], + "programmingLanguage": [ITEM, n.doap+"programming-language"], + "abstractNote": [ITEM, n.dcterms+"abstract"], + "type": [ITEM, n.dcterms+"type"], + "medium": [ITEM, n.dcterms+"medium"], + "title": [ITEM, n.dcterms+"title"], + "shortTitle": [ITEM, n.bibo+"shortTitle"], + "numPages": [ITEM, n.bibo+"numPages"], + "applicationNumber": [ITEM, n.z+"applicationNumber"], + "issuingAuthority": [ITEM, [n.bibo+"issuer", [[n.rdf+"type", n.foaf+"Organization"]], n.foaf+"name"]], + "filingDate": [ITEM, n.dcterms+"dateSubmitted"] +}; + +var AUTHOR_LIST = 1; +var EDITOR_LIST = 2; +var CONTRIBUTOR_LIST = 3; +var CREATOR_LISTS = { + 1:n.bibo+"authorList", + 2:n.bibo+"editorList", + 3:n.bibo+"contributorList" +}; + +var CREATORS = { + "author": [ITEM, AUTHOR_LIST, n.dcterms+"creator"], + "attorneyAgent": [ITEM, CONTRIBUTOR_LIST, n.z+"attorneyAgent"], + "bookAuthor": [CONTAINER, AUTHOR_LIST, n.dcterms+"creator"], + "castMember": [ITEM, CONTRIBUTOR_LIST, n.rel+"ACT"], + "commenter": [ITEM, CONTRIBUTOR_LIST, [n.sioct+"has_reply", [[n.rdf+"type", n.sioct+"Comment"]], n.dcterms+"creator"]], + "composer": [ITEM, CONTRIBUTOR_LIST, n.rel+"CMP"], + "contributor": [ITEM, CONTRIBUTOR_LIST, n.dcterms+"contributor"], + "cosponsor": [ITEM, CONTRIBUTOR_LIST, n.rel+"SPN"], + "counsel": [ITEM, CONTRIBUTOR_LIST, n.z+"counsel"], + "director": [ITEM, CONTRIBUTOR_LIST, n.bibo+"director"], + "editor": [SUBCONTAINER, EDITOR_LIST, n.bibo+"editor"], + "guest": [ITEM, CONTRIBUTOR_LIST, n.po+"participant"], + "interviewer": [ITEM, CONTRIBUTOR_LIST, n.bibo+"interviewer"], + "interviewee": [ITEM, CONTRIBUTOR_LIST, n.bibo+"interviewee"], + "performer": [ITEM, CONTRIBUTOR_LIST, n.bibo+"performer"], + "producer": [ITEM, CONTRIBUTOR_LIST, n.bibo+"producer"], + "recipient": [ITEM, CONTRIBUTOR_LIST, n.bibo+"recipient"], + "reviewedAuthor": [ITEM, CONTRIBUTOR_LIST, [n.bibo+"reviewOf", [], n.dcterms+"creator"]], + "scriptwriter": [ITEM, CONTRIBUTOR_LIST, n.rel+"AUS"], + "seriesEditor": [CONTAINER_SERIES, EDITOR_LIST, n.bibo+"editor"], + "translator": [SUBCONTAINER, CONTRIBUTOR_LIST, n.bibo+"translator"], + "wordsBy": [ITEM, CONTRIBUTOR_LIST, n.rel+"LYR"] +}; + +var SAME_ITEM_RELATIONS = [n.dcterms+"isPartOf", n.dcterms+"isVersionOf", n.bibo+"affirmedBy", + n.bibo+"presentedAt", n.bibo+"presents", n.bibo+"reproducedIn", + n.bibo+"reviewOf", n.bibo+"translationOf", n.bibo+"transcriptOf"]; + +/** COMMON FUNCTIONS **/ + +var BIBO_NS_LENGTH = n.bibo.length; +var RDF_TYPE = n.rdf+"type"; + +function getBlankNode(attachToNode, itemPredicate, blankNodePairs, create) { + // check if a node with the same relation and properties already exists + var blankNode = null; + // look for blank node + var statements1 = Zotero.RDF.getStatementsMatching(attachToNode, itemPredicate, undefined); + for each(var statement1 in statements1) { + // look for appropriate statements on the blank node + var testNode = statement1[2]; + var statements2 = true; + for each(var pair in blankNodePairs) { + statements2 = Zotero.RDF.getStatementsMatching(testNode, pair[0], pair[1], false, true); + if(!statements2) break; + } + if(statements2) { + // if statements are good, then this is our node + blankNode = testNode; + break; + } + } + + // if no suitable node exists, generate a new one and add blank node statements + if(!blankNode && create) { + blankNode = Zotero.RDF.newResource(); + Zotero.RDF.addStatement(attachToNode, itemPredicate, blankNode, false); + [Zotero.RDF.addStatement(blankNode, pair[0], pair[1], false) for each(pair in blankNodePairs)]; + } + + return blankNode; +} + +/** + * A class representing a Zotero-to-BIBO type mapping + * @property zoteroType {String} The corresponding Zotero type name + */ +Type = function(type, typeDefinition) { + this.zoteroType = type; + this[ITEM] = {"pairs":typeDefinition[0]}; + this[SUBCONTAINER] = typeDefinition[1] ? {"alwaysAdd":typeDefinition[1][0], + "predicate":typeDefinition[1][1], + "pairs":typeDefinition[1][2]} : null; + this[CONTAINER] = typeDefinition[2] ? {"alwaysAdd":typeDefinition[2][0], + "predicate":typeDefinition[2][1], + "pairs":typeDefinition[2][2]} : null; +} + +/** + * Score a node to determine how well it matches our type definition + * @returns {[Integer, Object]} The score, and an object containing ITEM, SUBCONTAINER, and + * CONTAINER nodes + */ +Type.prototype.getMatchScore = function(node) { + var nodes = {2:node}; + + // check item (+2 for each match, -1 for each nonmatch) + var score = 3*[true for each(pair in this[ITEM].pairs) if(Zotero.RDF.getStatementsMatching(node, pair[0], pair[1]))].length-this[ITEM].pairs.length; + // check subcontainer + [score, nodes[SUBCONTAINER]] = this._scoreNodeRelationship(node, this[SUBCONTAINER], score); + // check container + [score, nodes[CONTAINER]] = this._scoreNodeRelationship( + (nodes[SUBCONTAINER] ? nodes[SUBCONTAINER] : nodes[ITEM]), this[CONTAINER], score); + + if(!nodes[CONTAINER]) nodes[CONTAINER] = nodes[ITEM]; + if(!nodes[SUBCONTAINER]) nodes[SUBCONTAINER] = nodes[CONTAINER]; + + return [score, nodes]; +} + +/** + * Score a CONTAINER/SUBCONTAINTER node + * @returns {[Integer, Object]} The score, and the node (if it existed) + */ +Type.prototype._scoreNodeRelationship = function(node, definition, score) { + var subNode = null; + if(definition) { + statements = Zotero.RDF.getStatementsMatching(node, definition.predicate, null); + if(statements) { + var bestScore = -9999; + for each(var statement in statements) { + // +2 for each match, -1 for each nonmatch + var testScore = 3*[true for each(pair in definition.pairs) if(Zotero.RDF.getStatementsMatching(statement[2], pair[0], pair[1]))].length-definition.pairs.length; + if(testScore > bestScore) { + subNode = statement[2]; + bestScore = testScore; + } + } + score += bestScore; + } else if(definition.alwaysAdd) { + score -= definition.pairs.length; + } + } + return [score, subNode]; +} + +/** + * Get USERITEM and SERIES nodes for this type + */ +Type.prototype.getItemSeriesNodes = function(nodes) { + const seriesDefinition = {"alwaysAdd":true, "predicate":n.dcterms+"isPartOf", "pairs":[[n.rdf+"type", n.bibo+"Series"]]}; + + // get user item node + var stmt = Zotero.RDF.getStatementsMatching(null, n.res+"resource", nodes[ITEM]); + nodes[USERITEM] = stmt ? stmt[0][0] : nodes[ITEM]; + + // get ITEM_SERIES node + var score, subNode; + [score, subNode] = this._scoreNodeRelationship(nodes[ITEM], seriesDefinition, 0); + Zotero.debug("got itemSeries with score "+score); + if(score >= 1) nodes[ITEM_SERIES] = subNode; + + // get SUBCONTAINER_SERIES node + [score, subNode] = this._scoreNodeRelationship(nodes[SUBCONTAINER], seriesDefinition, 0); + Zotero.debug("got subcontainerSeries with score "+score); + if(score >= 1) nodes[CONTAINER_SERIES] = subNode; + + // get CONTAINER_SERIES node + [score, subNode] = this._scoreNodeRelationship(nodes[CONTAINER], seriesDefinition, 0); + Zotero.debug("got containerSeries with score "+score); + if(score >= 1) nodes[CONTAINER_SERIES] = subNode; +} + +/** + * Add triples to relate nodes. Called after all properties have been added, so we know which nodes + * need to be related. + */ +Type.prototype.addNodeRelations = function(nodes) { + // add node relations + for each(var i in [ITEM_SERIES, SUBCONTAINER_SERIES, CONTAINER_SERIES]) { + // don't add duplicate nodes + if(!this[i-3]) continue; + // don't add nodes with no arcs + if(!Zotero.RDF.getArcsOut(nodes[i])) continue; + Zotero.RDF.addStatement(nodes[i], RDF_TYPE, n.bibo+"Series", false); + Zotero.RDF.addStatement(nodes[i-3], n.dcterms+"isPartOf", nodes[i], false); + } + + for each(var i in [ITEM, SUBCONTAINER, CONTAINER]) { + if(nodes[i]) { + // find predicate + if(i == ITEM) { + var j = 1; + var predicate = n.res+"resource"; + } else if(i == SUBCONTAINER || i == CONTAINER) { + // don't add duplicate nodes + if(!this[i]) continue; + // don't add nodes with no arcs + if(!this[i][0] && !Zotero.RDF.getArcsOut(nodes[i])) { + nodes[i] = nodes[i-1]; + continue; + } + + var predicate = this[i].predicate; + } + + // add type + [Zotero.RDF.addStatement(nodes[i], pair[0], pair[1], false) + for each(pair in this[i].pairs)]; + + // add relation to parent + for(var j = i-1; j>1; j--) { + if(nodes[j] != nodes[i]) { + Zotero.RDF.addStatement(nodes[j], predicate, nodes[i], false); + break; + } + } + } + } +} + +/** + * Create USERITEM/ITEM/CONTAINER/SUBCONTAINER nodes for this type + * @returns {Object} The created nodes + */ +Type.prototype.createNodes = function(item) { + var nodes = {}; + nodes[USERITEM] = "#item_"+item.itemID; + + // come up with an item node URI + nodes[ITEM] = null; + // try the URL as URI + if(item.url) { + nodes[ITEM] = encodeURI(item.url); + if(usedURIs[nodes[ITEM]]) nodes[ITEM] = null; + } + // try the DOI as URI + if(!nodes[ITEM] && item.DOI) { + var doi = item.DOI; + if(doi.substr(0, 4) == "doi:") { + doi = doi.substr(4); + } else if(doi.substr(0, 8) == "urn:doi:") { + doi = doi.substr(8); + } else if(doi.substr(0, 9) == "info:doi/") { + doi = doi.substr(9); + } else if(doi.substr(0, 18) == "http://dx.doi.org/") { + doi = doi.substr(18); + } + nodes[ITEM] = "info:doi/"+encodeURI(doi); + if(usedURIs[nodes[ITEM]]) nodes[ITEM] = null; + } + // try the ISBN as URI + if(!nodes[ITEM] && item.ISBN) { + var isbn = item.ISBN.split(/, ?| /g)[0]; + nodes[ITEM] = "urn:isbn:"+encodeURI(isbn); + if(usedURIs[nodes[ITEM]]) nodes[ITEM] = null; + } + // no suitable item URI; fall back to a blank node + if(!nodes[ITEM]) nodes[ITEM] = Zotero.RDF.newResource(); + usedURIs[Zotero.RDF.getResourceURI(nodes[ITEM])] = true; + + // attach item node to user item node + Zotero.RDF.addStatement(nodes[USERITEM], RDF_TYPE, n.z+"UserItem", false); + Zotero.RDF.addStatement(nodes[USERITEM], n.res+"resource", nodes[ITEM], false); + + // container node + nodes[CONTAINER] = (this[CONTAINER] ? Zotero.RDF.newResource() : nodes[ITEM]); + + // subcontainer node + nodes[SUBCONTAINER] = (this[SUBCONTAINER] ? Zotero.RDF.newResource() : nodes[CONTAINER]); + + // series nodes + nodes[ITEM_SERIES] = Zotero.RDF.newResource(); + nodes[CONTAINER_SERIES] = (this[CONTAINER] ? Zotero.RDF.newResource() : nodes[ITEM_SERIES]); + nodes[SUBCONTAINER_SERIES] = (this[SUBCONTAINER] ? Zotero.RDF.newResource() : nodes[CONTAINER_SERIES]); + + return nodes; +} + +/** + * A class representing a BIBO-to-Zotero literal property mapping + */ +LiteralProperty = function(field) { + this.field = field; + this.mapping = FIELDS[field]; + if(!this.mapping) { + Zotero.debug("WARNING: unrecognized field "+field+" in Bibliontology RDF; mapping to Zotero namespace"); + this.mapping = [ITEM, n.z+field]; + } +} + +/** + * Maps property from a set of RDF nodes to an item + */ +LiteralProperty.prototype.mapToItem = function(newItem, nodes) { + if(typeof this.mapping[0] == "function") { // function case: triples returned + // check function case + var content = this.mapping[1](nodes); + if(!content) return false; + newItem[this.field] = content; + } else { + var node = nodes[this.mapping[0]]; + if(!node) return false; + var statements = getStatementsByDefinition(this.mapping[1], node); + if(!statements) return false; + newItem[this.field] = [stmt[2].toString() for each(stmt in statements)].join(", "); + } + return true; +} + +/** + * Maps property from an item to a set of RDF nodes + */ +LiteralProperty.prototype.mapFromItem = function(item, nodes) { + if(typeof this.mapping[0] == "function") { // function case: triples returned + // check function case + [Zotero.RDF.addStatement(nodes[triple[0]], triple[1], triple[2], triple[3]) + for each(triple in this.mapping[0](item))]; + } else if(typeof this.mapping[1] == "string") { // string case: simple predicate + Zotero.RDF.addStatement(nodes[this.mapping[0]], + this.mapping[1], item.uniqueFields[this.field], true); + } else { // array case: complex predicate + var blankNode = getBlankNode(nodes[this.mapping[0]], + this.mapping[1][0], this.mapping[1][1], true); + Zotero.RDF.addStatement(blankNode, this.mapping[1][2], item.uniqueFields[this.field], true); + } +} + +/** + * A class representing a BIBO-to-Zotero creator mapping + */ +CreatorProperty = function(field) { + this.field = field; + this.mapping = CREATORS[field]; +} + +/** + * Maps creator from an foaf:Agent + */ +CreatorProperty.prototype.mapToCreator = function(creatorNode, zoteroType) { + Zotero.debug("mapping "+Zotero.RDF.getResourceURI(creatorNode)+" to a creator"); + var lastNameStmt = Zotero.RDF.getStatementsMatching(creatorNode, n.foaf+"surname", null); + if(lastNameStmt) { // look for a person with a last name + creator = {lastName:lastNameStmt[0][2].toString()}; + var firstNameStmt = Zotero.RDF.getStatementsMatching(creatorNode, n.foaf+"givenname", null); + if(firstNameStmt) creator.firstName = firstNameStmt[0][2].toString(); + } else { + var nameStmt = Zotero.RDF.getStatementsMatching(creatorNode, n.foaf+"name", null); + if(nameStmt) { // an organization + creator = {lastName:nameStmt[0][2].toString(), fieldMode:1}; + } else { // an unnamed entity; ignore it + Zotero.debug("Dropping unnamed creator "+creatorNode.toString()); + return false; + } + } + + // birthYear and shortName + var birthStmt = Zotero.RDF.getStatementsMatching(creatorNode, n.foaf+"birthday", null, true); + if(birthStmt) creator.birthYear = birthStmt[2].toString(); + var nickStmt = Zotero.RDF.getStatementsMatching(creatorNode, n.foaf+"nick", null, true); + if(nickStmt) creator.shortName = nickStmt[2].toString(); + + if(this.field == "author") { + // could be another primary creator + var creatorsForType = Zotero.Utilities.getCreatorsForType(zoteroType); + if(creatorsForType.indexOf("author") == -1) { + creator.creatorType = creatorsForType[0]; + } + } else { + creator.creatorType = this.field; + } + return creator; +} + +/** + * Maps creators from a top-level (ITEM/SUBCONTAINER/CONTAINER/SERIES) node to a list + */ +CreatorProperty.prototype.mapToCreators = function(node, zoteroType) { + var creators = []; + var creatorNodes = []; + var statements = getStatementsByDefinition(this.mapping[2], node); + if(statements) { + for each(var stmt in statements) { + var creator = this.mapToCreator(stmt[2], zoteroType); + if(creator) { + creators.push(creator); + creatorNodes.push(stmt[2]); + } + } + } + return [creators, creatorNodes]; +} + +/** + * Maps property from a Zotero creator array to a set of RDF nodes + */ +CreatorProperty.prototype.mapFromCreator = function(item, creator, nodes) { + var creatorsForType = Zotero.Utilities.getCreatorsForType(item.itemType); + var isPrimary = creatorsForType[0] == this.field; + if(this.mapping) { + var mapping = this.mapping; + } else { + if(isPrimary && creatorsForType.indexOf("author") == -1) { + // treat other primary creators as dcterms:creators + var mapping = CREATORS["author"]; + } else { + Zotero.debug("WARNING: unrecognized creator type "+this.field+" in Bibliontology RDF; mapping to Zotero namespace"); + var mapping = [ITEM, AUTHOR_LIST, n.z+this.field]; + } + } + + var creatorNode = Zotero.RDF.newResource(); + if(creator.fieldMode == 1) { + Zotero.RDF.addStatement(creatorNode, RDF_TYPE, n.foaf+"Organization"); + if(creator.lastName) Zotero.RDF.addStatement(creatorNode, n.foaf+"name", creator.lastName, true); + } else { + Zotero.RDF.addStatement(creatorNode, RDF_TYPE, n.foaf+"Person"); + if(creator.firstName) Zotero.RDF.addStatement(creatorNode, n.foaf+"givenname", creator.firstName, true); + if(creator.lastName) Zotero.RDF.addStatement(creatorNode, n.foaf+"surname", creator.lastName, true); + } + if(creator.birthYear) Zotero.RDF.addStatement(creatorNode, n.foaf+"birthday", creator.birthYear, true); + if(creator.shortName) Zotero.RDF.addStatement(creatorNode, n.foaf+"nick", creator.shortName, true); + + // attach creator node + var attachTo = nodes[mapping[0]]; + if(typeof mapping[2] == "string") { + var relation = mapping[2]; + } else { + var relation = mapping[2][2]; + var attachTo = getBlankNode(attachTo, mapping[2][0], mapping[2][1], true); + } + Zotero.RDF.addStatement(attachTo, relation, creatorNode, false); + + // get appropriate creator list + var list = mapping[1]; + if(list == CONTRIBUTOR_LIST && isPrimary) { + // always attach primary to author list instead of contributor list + list = AUTHOR_LIST; + } + + // add to creator list + var creatorList = Zotero.RDF.getStatementsMatching(nodes[mapping[0]], CREATOR_LISTS[list], null); + if(creatorList) { + var creatorList = creatorList[0][2]; + } else { + var creatorList = Zotero.RDF.newResource(); + Zotero.RDF.newContainer("seq", creatorList); + Zotero.RDF.addStatement(nodes[mapping[0]], CREATOR_LISTS[list], creatorList, false); + } + Zotero.RDF.addContainerElement(creatorList, creatorNode, false); +} + +/** IMPORT FUNCTIONS **/ + +/** + * Gets statements matching a statement definition, if it exists + */ +function getStatementsByDefinition(definition, node) { + var statements = null; + if(typeof definition == "string") { // string case: simple predicate + statements = Zotero.RDF.getStatementsMatching(node, definition, null); + } else { // array case: complex + var blankNode = getBlankNode(node, definition[0], definition[1], false); + if(blankNode) { + statements = Zotero.RDF.getStatementsMatching(blankNode, definition[2], null); + } + } + return statements; +} + +function detectImport() { + // look for a bibo item type + var rdfTypes = Zotero.RDF.getStatementsMatching(null, RDF_TYPE, null); + if(rdfTypes) { + for each(var rdfType in rdfTypes) { + if(rdfType[2].uri && rdfType[2].uri.substr(0, BIBO_NS_LENGTH) == n.bibo) return true; + } + } + return false; +} + +function doImport() { + // collapse list of BIBO-only types + var collapsedTypes = {}; + for(var unprefixedBiboType in BIBO_TYPES) { + var biboType = n.bibo+unprefixedBiboType; + var type = new Type(BIBO_TYPES[unprefixedBiboType], [[[RDF_TYPE, n.bibo+biboType]], null, null]); + if(!collapsedTypes[biboType]) { + collapsedTypes[biboType] = [type]; + } else { + collapsedTypes[biboType].push(type); + } + } + + // collapse Zotero-to-BIBO type mappings + for(var zoteroType in TYPES) { + var type = new Type(zoteroType, TYPES[zoteroType]); + for each(var pair in TYPES[zoteroType][0]) { + if(!collapsedTypes[pair[1]]) { + collapsedTypes[pair[1]] = [type]; + } else { + collapsedTypes[pair[1]].push(type); + } + } + } + + // collapse list of field mappings + var collapsedProperties = {1:{}, 2:{}, 3:{}, 4:{}, 5:{}, 6:{}, 7:{}}; + var functionProperties = {}; + for(var zoteroField in FIELDS) { + if(typeof FIELDS[zoteroField][0] == "function") { + functionProperties[zoteroField] = new LiteralProperty(zoteroField); + } else { + var domain = FIELDS[zoteroField][0]; + var predicate = FIELDS[zoteroField][1]; + if(typeof predicate == "object") predicate = predicate[0]; + var prop = new LiteralProperty(zoteroField); + + if(collapsedProperties[domain][predicate]) { + collapsedProperties[domain][predicate].push(prop); + } else { + collapsedProperties[domain][predicate] = [prop]; + } + } + } + + // collapse list of creators + for(var creatorType in CREATORS) { + var domain = CREATORS[creatorType][0]; + var predicate = CREATORS[creatorType][2]; + if(typeof predicate == "object") predicate = predicate[0]; + var prop = new CreatorProperty(creatorType); + + if(collapsedProperties[domain][predicate]) { + collapsedProperties[domain][predicate].unshift(prop); + } else { + collapsedProperties[domain][predicate] = [prop]; + } + } + + // Go through all type arcs to find items + var itemNode, predicateNode, objectNode; + var rdfTypes = Zotero.RDF.getStatementsMatching(null, RDF_TYPE, null); + var itemNodes = {}; + for each(var rdfType in rdfTypes) { + [itemNode, predicateNode, objectNode] = rdfType; + if(!objectNode.uri || !collapsedTypes[objectNode.uri]) continue; + itemNodes[Zotero.RDF.getResourceURI(itemNode)] = itemNode; + } + + // Look through found items to see if their rdf:type matches a Zotero item type URI, and if so, + // subject to further processing + for each(var itemNode in itemNodes) { + // check whether the relationship to another item precludes us from extracting this as + // top-level + var skip = false; + for each(var arc in Zotero.RDF.getArcsIn(itemNode)) { + if(SAME_ITEM_RELATIONS.indexOf(arc) !== -1) { + skip = true; + break; + } + } + if(skip) continue; + + var itemRDFTypes = Zotero.RDF.getStatementsMatching(itemNode, RDF_TYPE, null); + + // score types by the number of triples they share with our types + var bestTypeScore = -9999; + var bestType, score, nodes, bestNodes; + for each(var rdfType in itemRDFTypes) { + if(!rdfType[2].uri) continue; + + for each(var type in collapsedTypes[rdfType[2].uri]) { + [score, nodes] = type.getMatchScore(itemNode); + Zotero.debug("Type "+type.zoteroType+" has score "+score); + + // check if this is the best we can do + if(score > bestTypeScore) { + bestTypeScore = score; + bestType = type; + bestNodes = nodes; + } + } + } + + // skip if this doesn't fit any type very well + if(bestTypeScore < 1) { + Zotero.debug("No good type mapping; best type was "+bestType.zoteroType+" with score "+bestTypeScore); + continue; + } + + Zotero.debug("Got item of type "+bestType.zoteroType+" with score "+bestTypeScore); + nodes = bestNodes; + bestType.getItemSeriesNodes(nodes); + Zotero.debug([i+" = "+nodes[i].toString() for(i in nodes)]); + + // create item + var zoteroType = bestType.zoteroType; + var newItem = new Zotero.Item(zoteroType); + + // handle ordinary properties + var allCreators = {} + for(var i in nodes) { + var propertiesHandled = {}; + var properties = Zotero.RDF.getArcsOut(nodes[i]); + for each(var property in properties) { + // only handle each property once + if(propertiesHandled[property]) continue; + propertiesHandled[property] = true; + Zotero.debug("handling "+property); + + var propertyMappings = collapsedProperties[i][property]; + Zotero.debug(propertyMappings); + if(propertyMappings) { + for each(var propertyMapping in propertyMappings) { + if(propertyMapping.mapToItem) { // LiteralProperty + propertyMapping.mapToItem(newItem, nodes); + } else if(propertyMapping.mapToCreator) { // CreatorProperty + var creators, creatorNodes; + [creators, creatorNodes] = propertyMapping.mapToCreators(nodes[i], zoteroType); + Zotero.debug(creators); + if(creators.length) { + for(var j in creators) { + var creatorNodeURI = Zotero.RDF.getResourceURI(creatorNodes[j]); + if(!allCreators[creatorNodeURI]) { + allCreators[creatorNodeURI] = creators[j]; + } + } + } + } + } + } + } + } + + // handle function properties + for each(var functionProperty in functionProperties) { + functionProperty.mapToItem(newItem, nodes); + } + + // get indicies of creators and add + var creatorLists = {}; + var creatorsAdded = {}; + for(var i in nodes) { + for(var j in CREATOR_LISTS) { + var statements = Zotero.RDF.getStatementsMatching(nodes[i], CREATOR_LISTS[j], null); + for each(var stmt in statements) { + var creatorListURI = Zotero.RDF.getResourceURI(stmt[2]); + if(creatorLists[creatorListURI]) continue; + creatorLists[creatorListURI] = true; + var creatorNodes = Zotero.RDF.getContainerElements(stmt[2]); + for each(var creatorNode in creatorNodes) { + var creatorNodeURI = Zotero.RDF.getResourceURI(creatorNode); + if(!creatorsAdded[creatorNodeURI]) { + creatorsAdded[creatorNodeURI] = true; + if(allCreators[creatorNodeURI]) { + // just add to creators list + newItem.creators.push(allCreators[creatorNodeURI]); + } else { + // creator not already processed, use default for this list type + if(j == AUTHOR_LIST) { + Zotero.debug("WARNING: creator in authorList lacks relationship to item in Bibliontology RDF; treating as primary creator"); + var prop = new CreatorProperty("author"); + } else if(j == EDITOR_LIST) { + Zotero.debug("WARNING: creator in editorList lacks relationship to item in Bibliontology RDF; treating as editor"); + var prop = new CreatorProperty("editor"); + } else { + Zotero.debug("WARNING: creator in contributorList lacks relationship to item in Bibliontology RDF; treating as contributor"); + var prop = new CreatorProperty("contributor"); + } + var creator = prop.mapToCreator(creatorNode, zoteroType); + if(creator) newItem.creators.push(creator); + } + } + } + } + } + } + + for(var creatorNodeURI in allCreators) { + if(!creatorsAdded[creatorNodeURI]) { + newItem.creators.push(allCreators[creatorNodeURI]); + } + } + + Zotero.debug(newItem); + newItem.complete(); + } +} + +/** EXPORT FUNCTIONS **/ + +var usedURIs = {}; + +function doExport() { + // add namespaces + [Zotero.RDF.addNamespace(i, n[i]) for(i in n)]; + + // compile references and create URIs + var item; + var items = {}; + while(item = Zotero.nextItem()) { + items[item.itemID] = item; + } + + // now that we've collected our items, start building the RDF + for each(var item in items) { + Zotero.debug(item); + // set type on item node + var type = new Type(item.itemType, TYPES[item.itemType]); + var nodes = type.createNodes(item); + Zotero.debug(nodes); + + // add fields + for(var field in item.uniqueFields) { + if(item.uniqueFields[field] == "") continue; + + var property = new LiteralProperty(field); + property.mapFromItem(item, nodes); + } + Zotero.debug("fields added"); + + // add creators + var creatorLists = []; + for each(var creator in item.creators) { + // create creator + var property = new CreatorProperty(creator.creatorType); + property.mapFromCreator(item, creator, nodes); + } + Zotero.debug("creators added"); + + type.addNodeRelations(nodes); + Zotero.debug("relations added"); + } +} +\ No newline at end of file