From e2993b94a4c31c90eacbe7aa11f80f534ced678f Mon Sep 17 00:00:00 2001 From: Simon Kornblith Date: Fri, 8 Jul 2011 03:42:26 +0000 Subject: [PATCH] - Use jsdom in node.js for unescaping HTML - Add itemToExportFormat and itemToServerJSON utility functions - Support asynchronous translator loading (this will only be used in node.js) - Fix cases where Safari/Chrome code was incorrectly applied in node.js - Add Zotero.ItemFields.getItemTypeFields() to connector cachedTypes.js --- .../zotero/xpcom/connector/cachedTypes.js | 22 +- .../zotero/xpcom/connector/translate_item.js | 113 +--------- .../zotero/xpcom/connector/translator.js | 27 +-- .../zotero/xpcom/translation/translate.js | 202 ++++++++++-------- .../xpcom/translation/translate_firefox.js | 7 +- .../xpcom/translation/translate_item.js | 28 +-- chrome/content/zotero/xpcom/utilities.js | 164 ++++++++++++++ 7 files changed, 303 insertions(+), 260 deletions(-) diff --git a/chrome/content/zotero/xpcom/connector/cachedTypes.js b/chrome/content/zotero/xpcom/connector/cachedTypes.js index 23365429e3..42a383c9df 100644 --- a/chrome/content/zotero/xpcom/connector/cachedTypes.js +++ b/chrome/content/zotero/xpcom/connector/cachedTypes.js @@ -48,7 +48,7 @@ Zotero.Connector_Types = new function() { this[schemaType][entry.name] = entry; } } - } + }; /** * Passes schema to a callback @@ -56,24 +56,24 @@ Zotero.Connector_Types = new function() { */ this.getSchema = function(callback) { callback(Zotero.Connector_Types.schema); - } + }; } Zotero.CachedTypes = function() { this.getID = function(idOrName) { if(!Zotero.Connector_Types[this.schemaType][idOrName]) return false; return Zotero.Connector_Types[this.schemaType][idOrName].id; - } + }; this.getName = function(idOrName) { if(!Zotero.Connector_Types[this.schemaType][idOrName]) return false; return Zotero.Connector_Types[this.schemaType][idOrName].name; - } + }; this.getLocalizedString = function(idOrName) { if(!Zotero.Connector_Types[this.schemaType][idOrName]) return false; return Zotero.Connector_Types[this.schemaType][idOrName].localizedString; - } + }; } Zotero.ItemTypes = new function() { @@ -90,7 +90,7 @@ Zotero.ItemTypes = new function() { } else if(Zotero.isSafari) { return safari.extension.baseURI+"images/itemTypes/"+Zotero.Connector_Types["itemTypes"][idOrName].icon; } - } + }; } Zotero.CreatorTypes = new function() { @@ -105,7 +105,7 @@ Zotero.CreatorTypes = new function() { creatorTypes.push(Zotero.Connector_Types["creatorTypes"][itemType.creatorTypes[i]]); } return creatorTypes; - } + }; } Zotero.ItemFields = new function() { @@ -119,7 +119,7 @@ Zotero.ItemFields = new function() { return Zotero.Connector_Types["itemTypes"][typeIdOrName].fields.indexOf( Zotero.Connector_Types["fields"][fieldIdOrName].id) !== -1; - } + }; this.getFieldIDFromTypeAndBase = function(itemType, baseField) { if(!Zotero.Connector_Types["fields"][baseField] @@ -138,5 +138,9 @@ Zotero.ItemFields = new function() { } return false; - } + }; + + this.getItemTypeFields = function(idOrName) { + return Zotero.Connector_Types["itemTypes"][typeIdOrName].fields.slice(); + }; } \ No newline at end of file diff --git a/chrome/content/zotero/xpcom/connector/translate_item.js b/chrome/content/zotero/xpcom/connector/translate_item.js index 6c9a76bbc1..7e86846784 100644 --- a/chrome/content/zotero/xpcom/connector/translate_item.js +++ b/chrome/content/zotero/xpcom/connector/translate_item.js @@ -56,120 +56,9 @@ Zotero.Translate.ItemSaver.prototype = { * Saves items to server */ "_saveToServer":function(items, callback) { - const IGNORE_FIELDS = ["seeAlso", "attachments", "complete"]; - var newItems = []; for(var i in items) { - var item = items[i]; - - var newItem = {}; - newItems.push(newItem); - - var typeID = Zotero.ItemTypes.getID(item.itemType); - if(!typeID) { - Zotero.debug("Translate: Invalid itemType "+item.itemType+"; saving as webpage"); - item.itemType = "webpage"; - typeID = Zotero.ItemTypes.getID(item.itemType); - } - - var fieldID; - for(var field in item) { - if(IGNORE_FIELDS.indexOf(field) !== -1) continue; - - var val = item[field]; - - if(field === "itemType") { - newItem[field] = val; - } else if(field === "creators") { - // normalize creators - var newCreators = newItem.creators = []; - for(var j in val) { - var creator = val[j]; - - // Single-field mode - if (!creator.firstName || (creator.fieldMode && creator.fieldMode == 1)) { - var newCreator = { - name: creator.lastName - }; - } - // Two-field mode - else { - var newCreator = { - firstName: creator.firstName, - lastName: creator.lastName - }; - } - - // ensure creatorType is present and valid - newCreator.creatorType = "author"; - if(creator.creatorType) { - if(Zotero.CreatorTypes.getID(creator.creatorType)) { - newCreator.creatorType = creator.creatorType; - } else { - Zotero.debug("Translate: Invalid creator type "+creator.creatorType+"; falling back to author"); - } - } - - newCreators.push(newCreator); - } - } else if(field === "tags") { - // normalize tags - var newTags = newItem.tags = []; - for(var j in val) { - var tag = val[j]; - if(typeof tag === "object") { - if(tag.tag) { - tag = tag.tag; - } else if(tag.name) { - tag = tag.name; - } else { - Zotero.debug("Translate: Discarded invalid tag"); - continue; - } - } - newTags.push({"tag":tag.toString(), "type":1}) - } - } else if(field === "notes") { - // normalize notes - var newNotes = newItem.notes = []; - for(var j in val) { - var note = val[j]; - if(typeof note === "object") { - if(!note.note) { - Zotero.debug("Translate: Discarded invalid note"); - continue; - } - note = note.note; - } - newNotes.push({"itemType":"note", "note":note.toString()}); - } - } else if(fieldID = Zotero.ItemFields.getID(field)) { - // if content is not a string, either stringify it or delete it - if(typeof val !== "string") { - if(val || val === 0) { - val = val.toString(); - } else { - continue; - } - } - - // map from base field if possible - var itemFieldID = Zotero.ItemFields.getFieldIDFromTypeAndBase(typeID, fieldID); - if(itemFieldID) { - newItem[Zotero.ItemFields.getName(itemFieldID)] = val; - continue; // already know this is valid - } - - // if field is valid for this type, set field - if(Zotero.ItemFields.isValidForType(fieldID, typeID)) { - newItem[field] = val; - } else { - Zotero.debug("Translate: Discarded field "+field+": field not valid for type "+item.itemType, 3); - } - } else if(field !== "complete") { - Zotero.debug("Translate: Discarded unknown field "+field, 3); - } - } + newItems.push(Zotero.Utilities.itemToServerJSON(items[i])); } var url = 'users/%%USERID%%/items?key=%%APIKEY%%'; diff --git a/chrome/content/zotero/xpcom/connector/translator.js b/chrome/content/zotero/xpcom/connector/translator.js index 38bb15121a..5226677c43 100644 --- a/chrome/content/zotero/xpcom/connector/translator.js +++ b/chrome/content/zotero/xpcom/connector/translator.js @@ -42,7 +42,7 @@ Zotero.Translators = new function() { this.init = function(translators) { if(!translators) { translators = []; - if(!Zotero.isFx && localStorage["translatorMetadata"]) { + if((Zotero.isChrome || Zotero.isSafari) && localStorage["translatorMetadata"]) { try { translators = JSON.parse(localStorage["translatorMetadata"]); if(typeof translators !== "object") { @@ -186,17 +186,17 @@ Zotero.Translators = new function() { if(j === 0) { converterFunctions.push(null); - } else if(Zotero.isFx) { + } else if(Zotero.isChrome || Zotero.isSafari) { + // in Chrome/Safari, the converterFunction needs to be passed as JSON, so + // just push an array with the proper and proxyHosts + converterFunctions.push([properHosts[j-1], proxyHosts[j-1]]); + } else { // in Firefox, push the converterFunction converterFunctions.push(new function() { var re = new RegExp('^https?://(?:[^/]\\.)?'+Zotero.Utilities.quotemeta(properHosts[j-1]), "gi"); var proxyHost = proxyHosts[j-1].replace(/\$/g, "$$$$"); return function(uri) { return uri.replace(re, "$&."+proxyHost) }; }); - } else { - // in Chrome/Safari, the converterFunction needs to be passed as JSON, so - // just push an array with the proper and proxyHosts - converterFunctions.push([properHosts[j-1], proxyHosts[j-1]]); } // don't add translator more than once @@ -244,17 +244,6 @@ Zotero.Translators = new function() { if(reset) { var serializedTranslators = newMetadata; - if(!Zotero.isFx) { - // clear cached translatorCode - Zotero.debug("Translators: Resetting translators"); - // XXX this is only to clear localStorage for people who installed yesterday and - // should disappear soon - for(var i in localStorage) { - if(i.substr(0, TRANSLATOR_CODE_PREFIX.length) === TRANSLATOR_CODE_PREFIX) { - delete localStorage[i]; - } - } - } } else { var serializedTranslators = []; var hasChanged = false; @@ -300,7 +289,7 @@ Zotero.Translators = new function() { } // Store - if(!Zotero.isFx) { + if(Zotero.isChrome || Zotero.isSafari) { localStorage["translatorMetadata"] = JSON.stringify(serializedTranslators); } @@ -440,7 +429,7 @@ Zotero.Translator.prototype.init = function(info) { } if(info.code) { - this.code = preprocessCode(info.code); + this.code = Zotero.Translators.preprocessCode(info.code); } else if(this.hasOwnProperty("code")) { delete this.code; } diff --git a/chrome/content/zotero/xpcom/translation/translate.js b/chrome/content/zotero/xpcom/translation/translate.js index fac8a74d5d..bd0c4e03b2 100644 --- a/chrome/content/zotero/xpcom/translation/translate.js +++ b/chrome/content/zotero/xpcom/translation/translate.js @@ -283,48 +283,48 @@ Zotero.Translate.Sandbox = { var sandbox; var haveTranslatorFunction = function(translator) { translation.translator[0] = translator; - if(!translation._loadTranslator(translator)) throw new Error("Translator could not be loaded"); - - if(Zotero.isFx) { - // do same origin check - var secMan = Components.classes["@mozilla.org/scriptsecuritymanager;1"] - .getService(Components.interfaces.nsIScriptSecurityManager); - var ioService = Components.classes["@mozilla.org/network/io-service;1"] - .getService(Components.interfaces.nsIIOService); - - var outerSandboxURI = ioService.newURI(typeof translate._sandboxLocation === "object" ? - translate._sandboxLocation.location : translate._sandboxLocation, null, null); - var innerSandboxURI = ioService.newURI(typeof translation._sandboxLocation === "object" ? - translation._sandboxLocation.location : translation._sandboxLocation, null, null); - - Zotero.debug(outerSandboxURI.spec); - Zotero.debug(innerSandboxURI.spec); - - try { - secMan.checkSameOriginURI(outerSandboxURI, innerSandboxURI, false); - } catch(e) { - throw new Error("getTranslatorObject() may not be called from web or search "+ - "translators to web or search translators from different origins."); + translation._loadTranslator(translator, function() { + if(Zotero.isFx) { + // do same origin check + var secMan = Components.classes["@mozilla.org/scriptsecuritymanager;1"] + .getService(Components.interfaces.nsIScriptSecurityManager); + var ioService = Components.classes["@mozilla.org/network/io-service;1"] + .getService(Components.interfaces.nsIIOService); + + var outerSandboxURI = ioService.newURI(typeof translate._sandboxLocation === "object" ? + translate._sandboxLocation.location : translate._sandboxLocation, null, null); + var innerSandboxURI = ioService.newURI(typeof translation._sandboxLocation === "object" ? + translation._sandboxLocation.location : translation._sandboxLocation, null, null); + + Zotero.debug(outerSandboxURI.spec); + Zotero.debug(innerSandboxURI.spec); + + try { + secMan.checkSameOriginURI(outerSandboxURI, innerSandboxURI, false); + } catch(e) { + throw new Error("getTranslatorObject() may not be called from web or search "+ + "translators to web or search translators from different origins."); + } } - } - - translation._prepareTranslation(); - setDefaultHandlers(translate, translation); - sandbox = translation._sandboxManager.sandbox; - if(sandbox.Export) { - sandbox.Export.Zotero = sandbox.Zotero; - sandbox = sandbox.Export; - } else { - translate._debug("COMPAT WARNING: "+translate.translator[0].label+" does "+ - "not export any properties. Only detect"+translate._entryFunctionSuffix+ - " and do"+translate._entryFunctionSuffix+" will be available in "+ - "connectors."); - } - - if(callback) { - callback(sandbox); - translate.decrementAsyncProcesses(); - } + + translation._prepareTranslation(); + setDefaultHandlers(translate, translation); + sandbox = translation._sandboxManager.sandbox; + if(sandbox.Export) { + sandbox.Export.Zotero = sandbox.Zotero; + sandbox = sandbox.Export; + } else { + translate._debug("COMPAT WARNING: "+translate.translator[0].label+" does "+ + "not export any properties. Only detect"+translate._entryFunctionSuffix+ + " and do"+translate._entryFunctionSuffix+" will be available in "+ + "connectors."); + } + + if(callback) { + callback(sandbox); + translate.decrementAsyncProcesses(); + } + }); }; if(typeof translation.translator[0] === "object") { @@ -929,27 +929,24 @@ Zotero.Translate.Base.prototype = { this._libraryID = libraryID; this._saveAttachments = saveAttachments === undefined || saveAttachments; + var me = this; if(typeof this.translator[0] === "object") { // already have a translator object, so use it - this._translateHaveTranslator(); + this._loadTranslator(this.translator[0], function() { me._translateTranslatorLoaded() }); } else { // need to get translator first - var me = this; Zotero.Translators.get(this.translator[0], function(translator) { me.translator[0] = translator; - me._translateHaveTranslator(); + me._loadTranslator(translator, function() { me._translateTranslatorLoaded() }); }); } }, /** - * Called when translator has been retrieved + * Called when translator has been retrieved and loaded */ - "_translateHaveTranslator":function() { - // load translators - if(!this._loadTranslator(this.translator[0])) return; - + "_translateTranslatorLoaded":function() { // set display options to default if they don't exist if(!this._displayOptions) this._displayOptions = this.translator[0].displayOptions; @@ -1091,7 +1088,7 @@ Zotero.Translate.Base.prototype = { }, /** - * Runs detect code for a translator + * Begins running detect code for a translator, first loading it */ "_detect":function() { // there won't be any translators if we need an RPC call @@ -1100,7 +1097,15 @@ Zotero.Translate.Base.prototype = { return; } - if(!this._loadTranslator(this._potentialTranslators[0])) return + var me = this; + this._loadTranslator(this._potentialTranslators[0], + function() { me._detectTranslatorLoaded() }); + }, + + /** + * Runs detect code for a translator + */ + "_detectTranslatorLoaded":function() { this._prepareDetection(); this.incrementAsyncProcesses(); @@ -1130,7 +1135,7 @@ Zotero.Translate.Base.prototype = { * @param {Zotero.Translator} translator * @return {Boolean} Whether the translator could be successfully loaded */ - "_loadTranslator":function(translator) { + "_loadTranslator":function(translator, callback) { var sandboxLocation = this._getSandboxLocation(); if(!this._sandboxLocation || sandboxLocation != this._sandboxLocation) { this._sandboxLocation = sandboxLocation; @@ -1155,10 +1160,9 @@ Zotero.Translate.Base.prototype = { } this.complete(false, "parse error"); - return false; } - return true; + if(callback) callback(); }, /** @@ -1494,9 +1498,13 @@ Zotero.Translate.Import.prototype.complete = function(returnValue, error) { * Get all potential import translators, ordering translators with the right file extension first */ Zotero.Translate.Import.prototype._getTranslatorsGetPotentialTranslators = function() { - var me = this; - Zotero.Translators.getImportTranslatorsForLocation(this.location, - function(translators) { me._getTranslatorsTranslatorsReceived(translators) }); + if(this.location) { + var me = this; + Zotero.Translators.getImportTranslatorsForLocation(this.location, + function(translators) { me._getTranslatorsTranslatorsReceived(translators) }); + } else { + Zotero.Translate.Base.prototype._getTranslatorsGetPotentialTranslators.call(this); + } } /** @@ -1507,10 +1515,13 @@ Zotero.Translate.Import.prototype.getTranslators = function() { if(!this._string && !this.location) { if(this._currentState === "detect") throw new Error("getTranslators: detection is already running"); this._currentState = "detect"; - this._foundTranslators = Zotero.Translators.getAllForType(this.type); - this._potentialTranslators = []; - this.complete(true); - return this._foundTranslators; + var me = this; + Zotero.Translators.getAllForType(this.type, function(translators) { + me._potentialTranslators = []; + me._foundTranslators = translators; + me.complete(true); + }); + if(this._currentState === null) return this._foundTranslators; } else { Zotero.Translate.Base.prototype.getTranslators.call(this); } @@ -1519,46 +1530,61 @@ Zotero.Translate.Import.prototype.getTranslators = function() { /** * Overload {@link Zotero.Translate.Base#_loadTranslator} to prepare translator IO */ -Zotero.Translate.Import.prototype._loadTranslator = function(translator) { +Zotero.Translate.Import.prototype._loadTranslator = function(translator, callback) { // call super - var returnVal = Zotero.Translate.Base.prototype._loadTranslator.call(this, translator); - if(!returnVal) return returnVal; + var me = this; + Zotero.Translate.Base.prototype._loadTranslator.call(this, translator, function() { + me._loadTranslatorPrepareIO(translator, callback); + }); +} +/** + * Prepare translator IO + */ +Zotero.Translate.Import.prototype._loadTranslatorPrepareIO = function(translator, callback) { var dataMode = (translator ? translator : this._potentialTranslators[0]).configOptions["dataMode"]; - var err = false; - if(this._io) { - try { - this._io.reset(dataMode); - } catch(e) { - err = e; + var me = this; + var initCallback = function(status, err) { + if(!status) { + me.complete(false, err); + } else { + me._sandboxManager.importObject(me._io); + if(callback) callback(); } - } else { + }; + + var err = false; + if(!this._io) { if(Zotero.Translate.IO.Read && this.location && this.location instanceof Components.interfaces.nsIFile) { try { - this._io = new Zotero.Translate.IO.Read(this.location, dataMode); + this._io = new Zotero.Translate.IO.Read(this.location); } catch(e) { err = e; } } else { try { - this._io = new Zotero.Translate.IO.String(this._string, this.path ? this.path : "", dataMode); + this._io = new Zotero.Translate.IO.String(this._string, this.path ? this.path : ""); } catch(e) { err = e; } } + + if(err) { + this.complete(false, err); + return; + } } + try { + this._io.init(dataMode, initCallback); + } catch(e) { + err = e; + } if(err) { - Zotero.debug("Translate: Preparing IO for "+translator.label+" failed: "); - Zotero.debug(err); this.complete(false, err); - return false; + return; } - - this._sandboxManager.importObject(this._io); - - return true; } /** @@ -1853,10 +1879,6 @@ Zotero.Translate.IO.String = function(string, uri, mode) { } this._stringPointer = 0; this._uri = uri; - - if(mode) { - this.reset(mode); - } } Zotero.Translate.IO.String.prototype = { @@ -1868,16 +1890,16 @@ Zotero.Translate.IO.String.prototype = { "_getXML":"r" }, - "_initRDF":function() { + "_initRDF":function(callback) { Zotero.debug("Translate: Initializing RDF data store"); this._dataStore = new Zotero.RDF.AJAW.RDFIndexedFormula(); + this.RDF = new Zotero.Translate.IO._RDFSandbox(this._dataStore); if(this._string.length) { var parser = new Zotero.RDF.AJAW.RDFParser(this._dataStore); parser.parse(Zotero.Translate.IO.parseDOMXML(this._string), this._uri); + callback(true); } - - this.RDF = new Zotero.Translate.IO._RDFSandbox(this._dataStore); }, "setCharacterSet":function(charset) {}, @@ -1936,12 +1958,14 @@ Zotero.Translate.IO.String.prototype = { } }, - "reset":function(newMode) { + "init":function(newMode, callback) { this._stringPointer = 0; this._mode = newMode; if(Zotero.Translate.IO.rdfDataModes.indexOf(this._mode) !== -1) { - this._initRDF(); + this._initRDF(callback); + } else { + callback(true); } }, diff --git a/chrome/content/zotero/xpcom/translation/translate_firefox.js b/chrome/content/zotero/xpcom/translation/translate_firefox.js index 9fcc528893..46524fc3be 100644 --- a/chrome/content/zotero/xpcom/translation/translate_firefox.js +++ b/chrome/content/zotero/xpcom/translation/translate_firefox.js @@ -325,9 +325,6 @@ Zotero.Translate.IO.Read = function(file, mode) { } Zotero.debug("Translate: Detected file charset as "+this._charset); - - // We know the charset now. Open a converter stream. - if(mode) this.reset(mode); } Zotero.Translate.IO.Read.prototype = { @@ -437,7 +434,7 @@ Zotero.Translate.IO.Read.prototype = { } }, - "reset":function(newMode) { + "init":function(newMode, callback) { if(Zotero.Translate.IO.maintainedInstances.indexOf(this) === -1) { Zotero.Translate.IO.maintainedInstances.push(this); } @@ -447,6 +444,8 @@ Zotero.Translate.IO.Read.prototype = { if(Zotero.Translate.IO.rdfDataModes.indexOf(this._mode) !== -1 && !this.RDF) { this._initRDF(); } + + callback(true); }, "close":function() { diff --git a/chrome/content/zotero/xpcom/translation/translate_item.js b/chrome/content/zotero/xpcom/translation/translate_item.js index 34cd653dd1..092d6fe086 100644 --- a/chrome/content/zotero/xpcom/translation/translate_item.js +++ b/chrome/content/zotero/xpcom/translation/translate_item.js @@ -664,33 +664,7 @@ Zotero.Translate.ItemGetter.prototype = { returnItemArray.date = Zotero.Date.multipartToStr(returnItemArray.date); } - returnItemArray.uniqueFields = {}; - - // get base fields, not just the type-specific ones - var itemTypeID = returnItem.itemTypeID; - var allFields = Zotero.ItemFields.getItemTypeFields(itemTypeID); - for each(var field in allFields) { - var fieldName = Zotero.ItemFields.getName(field); - - if(returnItemArray[fieldName] !== undefined) { - var baseField = Zotero.ItemFields.getBaseIDFromTypeAndField(itemTypeID, field); - - var baseName = null; - if(baseField && baseField != field) { - baseName = Zotero.ItemFields.getName(baseField); - } - - if(baseName) { - returnItemArray[baseName] = returnItemArray[fieldName]; - returnItemArray.uniqueFields[baseName] = returnItemArray[fieldName]; - } else { - returnItemArray.uniqueFields[fieldName] = returnItemArray[fieldName]; - } - } - } - - // preserve notes - if(returnItemArray.note) returnItemArray.uniqueFields.note = returnItemArray.note; + var returnItemArray = Zotero.Utilities.itemToExportFormat(returnItemArray); // TODO: Change tag.tag references in translators to tag.name // once translators are 1.5-only diff --git a/chrome/content/zotero/xpcom/utilities.js b/chrome/content/zotero/xpcom/utilities.js index e81ddc0247..fd3c5ea876 100644 --- a/chrome/content/zotero/xpcom/utilities.js +++ b/chrome/content/zotero/xpcom/utilities.js @@ -222,6 +222,17 @@ Zotero.Utilities = { var nsISUHTML = Components.classes["@mozilla.org/feed-unescapehtml;1"] .getService(Components.interfaces.nsIScriptableUnescapeHTML); return nsISUHTML.unescape(str); + } else if(Zotero.isNode) { + var doc = require('jsdom').jsdom(str, null, { + "features":{ + "FetchExternalResources":false, + "ProcessExternalResources":false, + "MutationEvents":false, + "QuerySelector":false + } + }); + if(!doc.documentElement) return str; + return doc.documentElement.textContent; } else { var node = document.createElement("div"); node.innerHTML = str; @@ -796,6 +807,159 @@ Zotero.Utilities = { dumped_text = "===>"+arr+"<===("+typeof(arr)+")"; } return dumped_text; + }, + + /** + * Adds all fields to an item in toArray() format and adds a unique (base) fields to + * uniqueFields array + */ + "itemToExportFormat":function(item) { + item.uniqueFields = {}; + + // get base fields, not just the type-specific ones + var itemTypeID = (item.itemTypeID ? item.itemTypeID : Zotero.ItemTypes.getID(item.itemType)); + var allFields = Zotero.ItemFields.getItemTypeFields(itemTypeID); + for(var i in allFields) { + var field = allFields[i]; + var fieldName = Zotero.ItemFields.getName(field); + + if(item[fieldName] !== undefined) { + var baseField = Zotero.ItemFields.getBaseIDFromTypeAndField(itemTypeID, field); + + var baseName = null; + if(baseField && baseField != field) { + baseName = Zotero.ItemFields.getName(baseField); + } + + if(baseName) { + item[baseName] = item[fieldName]; + item.uniqueFields[baseName] = item[fieldName]; + } else { + item.uniqueFields[fieldName] = item[fieldName]; + } + } + } + + // preserve notes + if(item.note) item.uniqueFields.note = item.note; + + return item; + }, + + /** + * Converts an item from toArray() format to content=json format used by the server + */ + "itemToServerJSON":function(item) { + const IGNORE_FIELDS = ["seeAlso", "attachments", "complete"]; + var newItem = {}; + + var typeID = Zotero.ItemTypes.getID(item.itemType); + if(!typeID) { + Zotero.debug("Translate: Invalid itemType "+item.itemType+"; saving as webpage"); + item.itemType = "webpage"; + typeID = Zotero.ItemTypes.getID(item.itemType); + } + + var fieldID; + for(var field in item) { + if(IGNORE_FIELDS.indexOf(field) !== -1) continue; + + var val = item[field]; + + if(field === "itemType") { + newItem[field] = val; + } else if(field === "creators") { + // normalize creators + var newCreators = newItem.creators = []; + for(var j in val) { + var creator = val[j]; + + // Single-field mode + if (!creator.firstName || (creator.fieldMode && creator.fieldMode == 1)) { + var newCreator = { + name: creator.lastName + }; + } + // Two-field mode + else { + var newCreator = { + firstName: creator.firstName, + lastName: creator.lastName + }; + } + + // ensure creatorType is present and valid + newCreator.creatorType = "author"; + if(creator.creatorType) { + if(Zotero.CreatorTypes.getID(creator.creatorType)) { + newCreator.creatorType = creator.creatorType; + } else { + Zotero.debug("Translate: Invalid creator type "+creator.creatorType+"; falling back to author"); + } + } + + newCreators.push(newCreator); + } + } else if(field === "tags") { + // normalize tags + var newTags = newItem.tags = []; + for(var j in val) { + var tag = val[j]; + if(typeof tag === "object") { + if(tag.tag) { + tag = tag.tag; + } else if(tag.name) { + tag = tag.name; + } else { + Zotero.debug("Translate: Discarded invalid tag"); + continue; + } + } + newTags.push({"tag":tag.toString(), "type":1}) + } + } else if(field === "notes") { + // normalize notes + var newNotes = newItem.notes = []; + for(var j in val) { + var note = val[j]; + if(typeof note === "object") { + if(!note.note) { + Zotero.debug("Translate: Discarded invalid note"); + continue; + } + note = note.note; + } + newNotes.push({"itemType":"note", "note":note.toString()}); + } + } else if(fieldID = Zotero.ItemFields.getID(field)) { + // if content is not a string, either stringify it or delete it + if(typeof val !== "string") { + if(val || val === 0) { + val = val.toString(); + } else { + continue; + } + } + + // map from base field if possible + var itemFieldID = Zotero.ItemFields.getFieldIDFromTypeAndBase(typeID, fieldID); + if(itemFieldID) { + newItem[Zotero.ItemFields.getName(itemFieldID)] = val; + continue; // already know this is valid + } + + // if field is valid for this type, set field + if(Zotero.ItemFields.isValidForType(fieldID, typeID)) { + newItem[field] = val; + } else { + Zotero.debug("Translate: Discarded field "+field+": field not valid for type "+item.itemType, 3); + } + } else if(field !== "complete") { + Zotero.debug("Translate: Discarded unknown field "+field, 3); + } + } + + return newItem; } }