{ "translatorID":"14763d25-8ba0-45df-8f52-b8d1108e7ac9", "translatorType":3, "label":"Bibliontology RDF", "creator":"Simon Kornblith", "target":"rdf", "minVersion":"2.0", "maxVersion":"", "priority":50, "browserSupport":"gcs", "configOptions":{"getCollections":"true", "dataMode":"rdf/xml"}, "displayOptions":{"exportNotes":true}, "inRepository":false, "lastUpdated":"2013-08-19 21:29:44" } var n = { address:"http://schemas.talis.com/2005/address/schema#", // could also use vcard? bibo:"http://purl.org/ontology/bibo/", ctag:"http://commontag.org/ns#", 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 : [, , ] Item classes should be in the form [[, ]+] This generates the triples (ITEM )+ Subcontainer and container classes should be in the form [, , [, ]*] | null If there is a property to be applied to the container, or if is true, then this generates ITEM CONTAINER (CONTAINER )* **/ // 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, [false, n.dcterms+"isPartOf", [[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, [false, n.dcterms+"isPartOf", [[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 : ([, ] | ) If a is specified, then it is passed the item and should return a set of triples in the form [[, , , ]*] where refers to one of the constants defined above. If is true, then is treated as a literal. If a is not used and is a string, then the parameters generate a triple in the form FIELD_CONTENT where refers to one of the constants defined above. Alternatively, may be an array in the form [, [, ]*, ] This generates the triples ( )* 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); var returnNumbers = []; for each(var number in priorityNumbers) { returnNumbers.push([ITEM, n.z+"priorityNumber", number, true]); } return returnNumbers; }, function(nodes) { var statements = Zotero.RDF.getStatementsMatching(nodes[ITEM], n.z+"priorityNumber", null); if(!statements) return false; var predicates = []; for each(var statement in statements) { predicates.push(statement[2]); } return predicates.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); for each(var pair in blankNodePairs) { Zotero.RDF.addStatement(blankNode, pair[0], pair[1], false); } } 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 = -this[ITEM].pairs.length; for each(var pair in this[ITEM].pairs) { if(Zotero.RDF.getStatementsMatching(node, pair[0], pair[1])) score += 3; } // 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 = -definition.pairs.length; for each(var pair in definition.pairs) { if(Zotero.RDF.getStatementsMatching(statement[2], pair[0], pair[1])) testScore += 3; } 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 for each(var pair in this[i].pairs) { Zotero.RDF.addStatement(nodes[i], pair[0], pair[1], false) } // add relation to parent for(var j = i-1; j>1; j--) { if(Zotero.RDF.getResourceURI(nodes[j]) != Zotero.RDF.getResourceURI(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.uri ? item.uri : "#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; var content = []; for each(var stmt in statements) { content.push(stmt[2].toString()); } newItem[this.field] = content.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 for each(var triple in this.mapping[0](item)) { Zotero.RDF.addStatement(nodes[triple[0]], triple[1], triple[2], triple[3]) } } 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(typeof rdfType[2] === "object" && Z.RDF.getResourceURI(rdfType[2]).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(typeof objectNode !== "object") continue; var uri = Zotero.RDF.getResourceURI(itemNode); if(!uri) continue; itemNodes[uri] = 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(typeof rdfType[2] !== "object") continue; var collapsedTypesForItem = collapsedTypes[Z.RDF.getResourceURI(rdfType[2])]; if(!collapsedTypesForItem) continue; for each(var type in collapsedTypesForItem) { [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); // 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]; 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); 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); } // handle creators and tags var creatorLists = {}; var creatorsAdded = {}; for(var i in nodes) { // take DC subjects as tags var statements = Zotero.RDF.getStatementsMatching(nodes[i], n.dcterms+"subject", null); for each(var stmt in statements) { // if attached to the user item, it's a user tag; otherwise, an automatic tag newItem.tags.push({tag:stmt[2], type:(i == USERITEM ? 0 : 1)}); } // also take ctags as tags var statements = Zotero.RDF.getStatementsMatching(nodes[i], n.ctag+"tagged", null); for each(var stmt in statements) { var tag = {type:0}; // AutoTags are automatic tags var types = Zotero.RDF.getStatementsMatching(stmt[2], n.rdf+"type", null); for each(var type in types) { var uri = Zotero.RDF.getResourceURI(type[2]); if([n.ctag+"AutoTag", n.ctag+"AuthorTag"].indexOf(uri) != -1) tag.type = 1; break; } // labels are tag content var labels = Zotero.RDF.getStatementsMatching(stmt[2], n.ctag+"label", null); if(labels.length) { tag.tag = labels[0][2]; newItem.tags.push(tag); } } 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]); } } newItem.complete(); } } /** EXPORT FUNCTIONS **/ var usedURIs = {}; function doExport() { // add namespaces for(var i in n) Zotero.RDF.addNamespace(i, n[i]); // compile references and create URIs var item; var items = {}; while(item = Zotero.nextItem()) { items[item.itemID] = item; } var autoTags = {}; var userTags = {}; // now that we've collected our items, start building the RDF for each(var item in items) { // set type on item node var type = new Type(item.itemType, TYPES[item.itemType]); var nodes = type.createNodes(item); // add fields for(var field in item.uniqueFields) { if(!item.uniqueFields[field] && item.uniqueFields[field] !== 0) 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"); // add tags for each(var tag in item.tags) { var tagCollection = tag.type == 0 ? userTags : autoTags; if(tagCollection[tag.tag]) { var tagNode = tagCollection[tag.tag]; } else { var tagNode = Zotero.RDF.newResource(); Zotero.RDF.addStatement(tagNode, n.rdf+"type", (tag.type == 0 ? n.ctag+"UserTag" : n.ctag+"AutoTag"), false); Zotero.RDF.addStatement(tagNode, n.ctag+"label", tag.tag, true); tagCollection[tag.tag] = tagNode; } Zotero.RDF.addStatement(nodes[USERITEM], n.ctag+"tagged", tagNode, false); } type.addNodeRelations(nodes); //Zotero.debug("relations added"); } }