{
	"translatorID": "14763d25-8ba0-45df-8f52-b8d1108e7ac9",
	"label": "Bibliontology RDF",
	"creator": "Simon Kornblith",
	"target": "rdf",
	"minVersion": "2.0",
	"maxVersion": "",
	"priority": 50,
	"configOptions": {
		"getCollections": "true",
		"dataMode": "rdf/xml"
	},
	"displayOptions": {
		"exportNotes": true
	},
	"inRepository": true,
	"translatorType": 3,
	"browserSupport": "gcsibv",
	"lastUpdated": "2022-09-30 10:56:50"
}

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
   
   <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,								[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
	
	<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 (var i=0; i<isbns.length; i++) {
			var isbn = isbns[i];
			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 = [];
		var propList = [n.bibo+"isbn13", n.bibo+"isbn10"];
		for (var i=0; i<propList.length; i++) {
			var statements = Zotero.RDF.getStatementsMatching(nodes[CONTAINER], propList[i], null);
			if (statements) {
				for (var j=0; j<statements.length; j++) {
					isbns.push(statements[j][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 (var i=0; i<priorityNumbers.length; i++) {
			returnNumbers.push([ITEM, n.z+"priorityNumber", priorityNumbers[i], true]);
		}
		return returnNumbers;
	}, function(nodes) {
		var statements = Zotero.RDF.getStatementsMatching(nodes[ITEM], n.z+"priorityNumber", null);
		if (!statements) return false;
		var predicates = [];
		for (var i=0; i<statements.length; i++) {
			predicates.push(statements[i][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 (var i=0; i<statements1.length; i++) {
		// look for appropriate statements on the blank node
		var testNode = statements1[i][2];
		var statements2 = true;
		for (var j=0; j<blankNodePairs.length; j++) {
			statements2 = Zotero.RDF.getStatementsMatching(testNode, blankNodePairs[j][0], blankNodePairs[j][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 (var i=0; i<blankNodePairs.length; i++) {
			Zotero.RDF.addStatement(blankNode, blankNodePairs[i][0], blankNodePairs[i][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 (var i=0; i<this[ITEM].pairs.length; i++) {
		var pair = this[ITEM].pairs[i];
		if (Zotero.RDF.getStatementsMatching(node, pair[0], pair[1])) score += 3;
	}
	
	// check subcontainer
	var sub = this._scoreNodeRelationship(node, this[SUBCONTAINER], score);
	score = sub[0];
	nodes[SUBCONTAINER] = sub[1];
	// check container
	sub = this._scoreNodeRelationship(
		(nodes[SUBCONTAINER] ? nodes[SUBCONTAINER] : nodes[ITEM]),
		this[CONTAINER], score
	);
	score = sub[0];
	nodes[CONTAINER] = sub[1];
	
	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 (var i=0; i<statements.length; i++) {
				var statement = statements[i];
				// +2 for each match, -1 for each nonmatch
				var testScore = -definition.pairs.length;
				for (var j=0; j<definition.pairs.length; j++) {
					var pair = definition.pairs[j];
					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 tmp = this._scoreNodeRelationship(nodes[ITEM], seriesDefinition, 0);
	var score = tmp[0];
	var subNode = tmp[1];
	
	//Zotero.debug("got itemSeries with score "+score);
	if (score >= 1) nodes[ITEM_SERIES] = subNode;
	
	// get SUBCONTAINER_SERIES node
	tmp = this._scoreNodeRelationship(nodes[SUBCONTAINER], seriesDefinition, 0);
	score = tmp[0];
	subNode = tmp[1];
	
	//Zotero.debug("got subcontainerSeries with score "+score);
	if (score >= 1) nodes[CONTAINER_SERIES] = subNode;
	
	// get CONTAINER_SERIES node
	tmp = this._scoreNodeRelationship(nodes[CONTAINER], seriesDefinition, 0);
	score = tmp[0];
	subNode = tmp[1];
	
	//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
	var list = [ITEM_SERIES, SUBCONTAINER_SERIES, CONTAINER_SERIES];
	for (var h=0; h<list.length; h++) {
		var i=list[h];
		// 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);
	}
	
	var list = [ITEM, SUBCONTAINER, CONTAINER];
	for (var h=0; h<list.length; h++) {
		var i=list[h];
		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 (var j=0; j<this[i].pairs.length; j++) {
				var pair = this[i].pairs[j];
				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 (var i=0; i<statements.length; i++) {
			content.push(statements[i][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
		var triples = this.mapping[0](item);
		for (i=0; i<triples.length; i++) {
			Zotero.RDF.addStatement(nodes[triples[i][0]], triples[i][1], triples[i][2], triples[i][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)
			|| 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 (var i=0; i<statements.length; i++) {
			var stmt = statements[i];
			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
	let rdfTypes = null;
	try {
		rdfTypes = Zotero.RDF.getStatementsMatching(null, RDF_TYPE, null);
	}
	catch (err) {
		// probably just not RDF
		return false;
	}

	if (rdfTypes) {
		for (var i=0; i<rdfTypes.length; i++) {
			if (typeof rdfTypes[i][2] === "object" && Z.RDF.getResourceURI(rdfTypes[i][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 (var i=0; i<TYPES[zoteroType][0].length; i++) {
			var pair = TYPES[zoteroType][0][i];
			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 = {}, tmp;
	for (var i=0; i<rdfTypes.length; i++) {
		var rdfType = rdfTypes[i];
		tmp = rdfType;
		itemNode = tmp[0];
		predicateNode = tmp[1];
		objectNode = tmp[2];
		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 (var h in itemNodes) {
		var itemNode = itemNodes[h];
		// check whether the relationship to another item precludes us from extracting this as
		// top-level
		var skip = false;
		var arcs = Zotero.RDF.getArcsIn(itemNode);
		for (var j=0; j<arcs.length; j++) {
			if (SAME_ITEM_RELATIONS.indexOf( arcs[j] ) !== -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 (var j=0; j<itemRDFTypes.length; j++) {
			var rdfType = itemRDFTypes[j];
			if (typeof rdfType[2] !== "object") continue;
			var collapsedTypesForItem = collapsedTypes[Z.RDF.getResourceURI(rdfType[2])];
			if (!collapsedTypesForItem) continue;
			
			for (var k=0; k<collapsedTypesForItem.length; k++) {
				var type = collapsedTypesForItem[k];
				tmp = type.getMatchScore(itemNode);
				score = tmp[0];
				nodes = tmp[1];
				//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 (var g=0; g<properties.length; g++) {
				var property = properties[g];
				// only handle each property once
				if (propertiesHandled[property]) continue;
				propertiesHandled[property] = true;
				//Zotero.debug("handling "+property);
				
				var propertyMappings = collapsedProperties[i][property];
				if (propertyMappings) {
					for (var k=0; k<propertyMappings.length; k++) {
						var propertyMapping = propertyMappings[k];
						if (propertyMapping.mapToItem) {				// LiteralProperty
							propertyMapping.mapToItem(newItem, nodes);
						} else if (propertyMapping.mapToCreator) {	// CreatorProperty
							var creators, creatorNodes;
							tmp = propertyMapping.mapToCreators(nodes[i], zoteroType);
							creators = tmp[0];
							creatorNodes = tmp[1];
							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 (var j in functionProperties) {
			functionProperties[j].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 (var j=0; j<statements.length; j++) {
				var stmt = statements[j];
				// 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 (var j=0; j<statements.length; j++) {
				var stmt = statements[j];
				var tag = {type:0};
				
				// AutoTags are automatic tags
				var types = Zotero.RDF.getStatementsMatching(stmt[2], n.rdf+"type", null);
				for (var k=0; k<types.length; k++) {
					var type = types[k];
					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 (var k=0; k<statements.length; k++) {
					var stmt = statements[k];
					var creatorListURI = Zotero.RDF.getResourceURI(stmt[2]);
					if (creatorLists[creatorListURI]) continue;
					creatorLists[creatorListURI] = true;
					var creatorNodes = Zotero.RDF.getContainerElements(stmt[2]);
					for (var l=0; l<creatorNodes.length; l++) {
						var creatorNode = creatorNodes[l];
						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()) {
		// Skip standalone notes
		if (item.itemType == 'note') {
			continue;
		}
		items[item.itemID] = item;
	}
	var autoTags = {};
	var userTags = {};
	
	// now that we've collected our items, start building the RDF
	for (var h in items) {
		var item = items[h]; 
		// 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 (var i=0; i<item.creators.length; i++) {
			var creator = item.creators[i];
			// create creator
			var property = new CreatorProperty(creator.creatorType);
			property.mapFromCreator(item, creator, nodes);
		}
		//Zotero.debug("creators added");
		
		// add tags
		for (var i=0; i<item.tags.length; i++) {
			var tag = item.tags[i];
			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");
	}
}
/** BEGIN TEST CASES **/
var testCases = [
	{
		"type": "import",
		"input": "<rdf:RDF\n xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\"\n xmlns:res=\"http://purl.org/vocab/resourcelist/schema#\"\n xmlns:z=\"http://www.zotero.org/namespaces/export#\"\n xmlns:bibo=\"http://purl.org/ontology/bibo/\"\n xmlns:dcterms=\"http://purl.org/dc/terms/\"\n xmlns:foaf=\"http://xmlns.com/foaf/0.1/\">\n    <z:UserItem rdf:about=\"http://zotero.org/users/96641/items/XW66MVF4\">\n        <res:resource rdf:resource=\"http://libreas.eu/ausgabe29/05kim/\"/>\n        <z:accessDate>2017-05-28 14:08:38</z:accessDate>\n        <z:repository>libreas.eu</z:repository>\n    </z:UserItem>\n    <bibo:AcademicArticle rdf:about=\"http://libreas.eu/ausgabe29/05kim/\">\n        <bibo:uri>http://libreas.eu/ausgabe29/05kim/</bibo:uri>\n        <dcterms:language>de</dcterms:language>\n        <dcterms:abstract>Im Folgenden soll aufgezeigt werden, wie derzeit das Literaturverwaltungsprogramm Zotero innerhalb des Index Theologicus genutzt wird, um unselbstständige Literatur in einem bibliothekarischen Katalogisierungssystem zu erfassen. Die modulare und flexible Architektur der Open Source Software erlaubt es, die bereits kollaborativ zusammengetragene Programmierarbeit zur Datenextraktion mitzunutzen. Das vorgestellte semiautomatische Verfahren bringt auch bei der Verknüpfung von Normdaten erhebliche Vorteile für die Medienbearbeitung. &lt;/br&gt; &lt;b&gt;Schlüsselwörter&lt;/b&gt;: Literaturverwaltungsprogramm, Zotero, Katalogisierung, Unselbständige Werke, Aufsatzliteratur, Index Theologicus, Online-Bibliographie, WinIBW, Fachinformationsdienst Theologie &lt;hr&gt; This article presents an approach to use the reference management software Zotero within the theological article database Index Theologicus to catalogue article metadata for a library management system. Zotero's Open Source nature and flexible architecture allowed us to seamlessly reuse the vast amount of data extraction routines collaboratively developed for the software. We will show how the semi-automatic workflow we developed will make authority linking fun again. &lt;/br&gt; &lt;b&gt;Keywords:&lt;/b&gt; Reference Management System, Zotero, Cataloguing, Journal articles, Index Theologicus, Theological database, Academic Information Services for Theology</dcterms:abstract>\n        <dcterms:title>Semiautomatische Katalogisierung und Normdatenverknüpfung mit Zotero im Index Theologicus</dcterms:title>\n        <dcterms:creator rdf:nodeID=\"n5\"/>\n        <bibo:authorList>\n           <rdf:Seq><rdf:li rdf:nodeID=\"n5\"/><rdf:li rdf:nodeID=\"n7\"/></rdf:Seq>\n        </bibo:authorList>\n        <dcterms:creator rdf:nodeID=\"n7\"/>\n        <dcterms:isPartOf>\n            <bibo:Issue>\n                <bibo:issue>29</bibo:issue>\n                <dcterms:date>2016</dcterms:date>\n                <dcterms:isPartOf>\n                    <bibo:Journal>\n                        <dcterms:title>LIBREAS. Library Ideas</dcterms:title>\n                        <bibo:issn>1860-7950</bibo:issn>\n                    </bibo:Journal>\n                </dcterms:isPartOf>\n            </bibo:Issue>\n        </dcterms:isPartOf>\n    </bibo:AcademicArticle>\n    <foaf:Person rdf:nodeID=\"n5\">\n        <foaf:givenname>Timotheus Chang-whae</foaf:givenname>\n        <foaf:surname>Kim</foaf:surname>\n    </foaf:Person>\n    <foaf:Person rdf:nodeID=\"n7\">\n        <foaf:givenName>Philipp</foaf:givenName>\n        <foaf:surname>Zumstein</foaf:surname>\n    </foaf:Person>\n</rdf:RDF>\n",
		"items": [
			{
				"itemType": "journalArticle",
				"title": "Semiautomatische Katalogisierung und Normdatenverknüpfung mit Zotero im Index Theologicus",
				"creators": [
					{
						"lastName": "Kim",
						"firstName": "Timotheus Chang-whae"
					},
					{
						"lastName": "Zumstein",
						"firstName": "Philipp"
					}
				],
				"date": "2016",
				"ISSN": "1860-7950",
				"abstractNote": "Im Folgenden soll aufgezeigt werden, wie derzeit das Literaturverwaltungsprogramm Zotero innerhalb des Index Theologicus genutzt wird, um unselbstständige Literatur in einem bibliothekarischen Katalogisierungssystem zu erfassen. Die modulare und flexible Architektur der Open Source Software erlaubt es, die bereits kollaborativ zusammengetragene Programmierarbeit zur Datenextraktion mitzunutzen. Das vorgestellte semiautomatische Verfahren bringt auch bei der Verknüpfung von Normdaten erhebliche Vorteile für die Medienbearbeitung. </br> <b>Schlüsselwörter</b>: Literaturverwaltungsprogramm, Zotero, Katalogisierung, Unselbständige Werke, Aufsatzliteratur, Index Theologicus, Online-Bibliographie, WinIBW, Fachinformationsdienst Theologie <hr> This article presents an approach to use the reference management software Zotero within the theological article database Index Theologicus to catalogue article metadata for a library management system. Zotero's Open Source nature and flexible architecture allowed us to seamlessly reuse the vast amount of data extraction routines collaboratively developed for the software. We will show how the semi-automatic workflow we developed will make authority linking fun again. </br> <b>Keywords:</b> Reference Management System, Zotero, Cataloguing, Journal articles, Index Theologicus, Theological database, Academic Information Services for Theology",
				"issue": "29",
				"language": "de",
				"libraryCatalog": "libreas.eu",
				"publicationTitle": "LIBREAS. Library Ideas",
				"url": "http://libreas.eu/ausgabe29/05kim/",
				"attachments": [],
				"tags": [],
				"notes": [],
				"seeAlso": []
			}
		]
	}
]
/** END TEST CASES **/
