Use Zotero.Item.fromJSON() for saving from translators
Also: - Move some canonicalization of items returned by translators to Zotero.Translate - Make Zotero.Translate#translate return a promise - Add tests
This commit is contained in:
parent
e10deedc7e
commit
3fc38d750b
5 changed files with 921 additions and 338 deletions
|
@ -90,14 +90,13 @@ Zotero.Translate.Sandbox = {
|
|||
|
||||
const allowedObjects = ["complete", "attachments", "seeAlso", "creators", "tags", "notes"];
|
||||
|
||||
delete item.complete;
|
||||
// Create a new object here, so that we strip the "complete" property
|
||||
// (But don't create a new object if we're in a child translator, since that
|
||||
// would be a problem for the sandbox)
|
||||
var newItem = translate._parentTranslator ? item : {};
|
||||
for(var i in item) {
|
||||
var val = item[i];
|
||||
if(!val && val !== 0) {
|
||||
// remove null, undefined, and false properties, and convert objects to strings
|
||||
delete item[i];
|
||||
continue;
|
||||
}
|
||||
if(i === "complete" || (!val && val !== 0)) continue;
|
||||
|
||||
var type = typeof val;
|
||||
var isObject = type === "object" || type === "xml" || type === "function",
|
||||
|
@ -105,15 +104,18 @@ Zotero.Translate.Sandbox = {
|
|||
if(isObject && !shouldBeObject) {
|
||||
// Convert things that shouldn't be objects to objects
|
||||
translate._debug("Translate: WARNING: typeof "+i+" is "+type+"; converting to string");
|
||||
item[i] = val.toString();
|
||||
newItem[i] = val.toString();
|
||||
} else if(shouldBeObject && !isObject) {
|
||||
translate._debug("Translate: WARNING: typeof "+i+" is "+type+"; converting to array");
|
||||
item[i] = [val];
|
||||
newItem[i] = [val];
|
||||
} else if(type === "string") {
|
||||
// trim strings
|
||||
item[i] = val.trim();
|
||||
newItem[i] = val.trim();
|
||||
} else {
|
||||
newItem[i] = val;
|
||||
}
|
||||
}
|
||||
item = newItem;
|
||||
|
||||
// Clean empty creators
|
||||
if (item.creators) {
|
||||
|
@ -125,6 +127,9 @@ Zotero.Translate.Sandbox = {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Canonicalize tags
|
||||
if(item.tags) item.tags = translate._cleanTags(item.tags);
|
||||
|
||||
// if we're not supposed to save the item or we're in a child translator,
|
||||
// just return the item array
|
||||
|
@ -137,15 +142,35 @@ Zotero.Translate.Sandbox = {
|
|||
// We use this within the connector to keep track of items as they are saved
|
||||
if(!item.id) item.id = Zotero.Utilities.randomString();
|
||||
|
||||
// don't save documents as documents in connector, since we can't pass them around
|
||||
if(Zotero.isConnector) {
|
||||
if(item.attachments) {
|
||||
var attachments = item.attachments;
|
||||
var nAttachments = attachments.length;
|
||||
for(var j=0; j<nAttachments; j++) {
|
||||
if(attachments[j].document) {
|
||||
attachments[j].url = attachments[j].document.documentURI || attachments[j].document.URL;
|
||||
attachments[j].mimeType = "text/html";
|
||||
delete attachments[j].document;
|
||||
for(var j=0; j<attachments.length; j++) {
|
||||
var attachment = attachments[j];
|
||||
|
||||
// Don't save documents as documents in connector, since we can't pass them around
|
||||
if(Zotero.isConnector && attachment.document) {
|
||||
attachment.url = attachment.document.documentURI || attachment.document.URL;
|
||||
attachment.mimeType = "text/html";
|
||||
delete attachment.document;
|
||||
}
|
||||
|
||||
// Canonicalize tags
|
||||
if(attachment.tags !== undefined) attachment.tags = translate._cleanTags(attachment.tags);
|
||||
}
|
||||
}
|
||||
|
||||
if(item.notes) {
|
||||
var notes = item.notes;
|
||||
for(var j=0; j<notes.length; j++) {
|
||||
var note = notes[j];
|
||||
if(!note) {
|
||||
notes.splice(j--, 1);
|
||||
} else if(typeof(note) == "object") {
|
||||
// Canonicalize tags
|
||||
if(note.tags !== undefined) note.tags = translate._cleanTags(note.tags);
|
||||
} else {
|
||||
// Convert to object
|
||||
notes[j] = {"note":note.toString()}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -320,7 +345,7 @@ Zotero.Translate.Sandbox = {
|
|||
errorHandlerSet = true;
|
||||
translation.setHandler("error", function(obj, error) { translate.complete(false, error) });
|
||||
}
|
||||
return translation.translate(false);
|
||||
translation.translate(false);
|
||||
};
|
||||
|
||||
safeTranslator.getTranslatorObject = function(callback) {
|
||||
|
@ -717,13 +742,9 @@ Zotero.Translate.Sandbox = {
|
|||
* @param {SandboxCollection} collection
|
||||
*/
|
||||
"_collectionDone":function(translate, collection) {
|
||||
translate.newCollections.push(collection);
|
||||
if(translate._libraryID == false) {
|
||||
translate.newCollections.push(collection);
|
||||
translate._runHandler("collectionDone", collection);
|
||||
} else {
|
||||
var newCollection = translate._itemSaver.saveCollection(collection);
|
||||
translate.newCollections.push(newCollection);
|
||||
translate._runHandler("collectionDone", newCollection);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -1139,6 +1160,8 @@ Zotero.Translate.Base.prototype = {
|
|||
* or NULL for default library;
|
||||
* if FALSE, don't save items
|
||||
* @param {Boolean} [saveAttachments=true] Exclude attachments (e.g., snapshots) on import
|
||||
* @returns {Promise} Promise resolved with saved items
|
||||
* when translation complete
|
||||
*/
|
||||
"translate":function(libraryID, saveAttachments) { // initialize properties specific to each translation
|
||||
if(!this.translator || !this.translator.length) {
|
||||
|
@ -1163,6 +1186,22 @@ Zotero.Translate.Base.prototype = {
|
|||
this._savingAttachments = [];
|
||||
this._savingItems = 0;
|
||||
this._waitingForSave = false;
|
||||
|
||||
// Attach handlers for promise
|
||||
var me = this,
|
||||
deferred = Zotero.Promise.defer();
|
||||
var doneHandler = function (obj, returnValue) {
|
||||
if (returnValue) deferred.resolve(me.newItems);
|
||||
me.removeHandler("done", doneHandler);
|
||||
me.removeHandler("error", errorHandler);
|
||||
};
|
||||
var errorHandler = function (obj, error) {
|
||||
deferred.reject(error);
|
||||
me.removeHandler("done", doneHandler);
|
||||
me.removeHandler("error", errorHandler);
|
||||
};
|
||||
this.setHandler("done", doneHandler);
|
||||
this.setHandler("error", errorHandler);
|
||||
|
||||
var me = this;
|
||||
if(typeof this.translator[0] === "object") {
|
||||
|
@ -1174,6 +1213,7 @@ Zotero.Translate.Base.prototype = {
|
|||
this.translator[0] = translator;
|
||||
this._loadTranslator(translator).then(function() { me._translateTranslatorLoaded() });
|
||||
}
|
||||
return deferred.promise;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -1368,6 +1408,31 @@ Zotero.Translate.Base.prototype = {
|
|||
|
||||
return errorString;
|
||||
},
|
||||
|
||||
/**
|
||||
* Canonicalize an array of tags such that they are all objects with the tag stored in the
|
||||
* "tag" property and a type (if specified) is stored in the "type" property
|
||||
* @returns {Object[]} Array of new tag objects
|
||||
*/
|
||||
"_cleanTags":function(tags) {
|
||||
var newTags = [];
|
||||
if(!tags) return newTags;
|
||||
for(var i=0; i<tags.length; i++) {
|
||||
var tag = tags[i];
|
||||
if(!tag) continue;
|
||||
if(typeof(tag) == "object") {
|
||||
var tagString = tag.tag || tag.name;
|
||||
if(tagString) {
|
||||
var newTag = {"tag":tagString};
|
||||
if(tag.type) newTag.type = tag.type;
|
||||
newTags.push(newTag);
|
||||
}
|
||||
} else {
|
||||
newTags.push({"tag":tag.toString()});
|
||||
}
|
||||
}
|
||||
return newTags;
|
||||
},
|
||||
|
||||
/**
|
||||
* Saves items to the database, taking care to defer attachmentProgress notifications
|
||||
|
@ -1443,7 +1508,18 @@ Zotero.Translate.Base.prototype = {
|
|||
*/
|
||||
"_checkIfDone":function() {
|
||||
if(!this._savingItems && !this._savingAttachments.length && (!this._currentState || this._waitingForSave)) {
|
||||
this._runHandler("done", true);
|
||||
if(this.newCollections) {
|
||||
var me = this;
|
||||
this._itemSaver.saveCollections(this.newCollections).then(function (newCollections) {
|
||||
me.newCollections = newCollections;
|
||||
me._runHandler("done", true);
|
||||
}, function (err) {
|
||||
me._runHandler("error", err);
|
||||
me._runHandler("done", false);
|
||||
});
|
||||
} else {
|
||||
this._runHandler("done", true);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -1737,9 +1813,13 @@ Zotero.Translate.Web.prototype._getParameters = function() {
|
|||
* Prepare translation
|
||||
*/
|
||||
Zotero.Translate.Web.prototype._prepareTranslation = function() {
|
||||
this._itemSaver = new Zotero.Translate.ItemSaver(this._libraryID,
|
||||
Zotero.Translate.ItemSaver[(this._saveAttachments ? "ATTACHMENT_MODE_DOWNLOAD" : "ATTACHMENT_MODE_IGNORE")], 1,
|
||||
this.document, this._cookieSandbox, this.location);
|
||||
this._itemSaver = new Zotero.Translate.ItemSaver({
|
||||
"libraryID":this._libraryID,
|
||||
"attachmentMode":Zotero.Translate.ItemSaver[(this._saveAttachments ? "ATTACHMENT_MODE_DOWNLOAD" : "ATTACHMENT_MODE_IGNORE")],
|
||||
"forceTagType":1,
|
||||
"cookieSandbox":this._cookieSandbox,
|
||||
"baseURI":this.location
|
||||
});
|
||||
this.newItems = [];
|
||||
}
|
||||
|
||||
|
@ -1748,7 +1828,7 @@ Zotero.Translate.Web.prototype._prepareTranslation = function() {
|
|||
*/
|
||||
Zotero.Translate.Web.prototype.translate = function(libraryID, saveAttachments, selectedItems) {
|
||||
this._selectedItems = selectedItems;
|
||||
Zotero.Translate.Base.prototype.translate.apply(this, [libraryID, saveAttachments]);
|
||||
return Zotero.Translate.Base.prototype.translate.apply(this, [libraryID, saveAttachments]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -2044,10 +2124,12 @@ Zotero.Translate.Import.prototype._prepareTranslation = function() {
|
|||
getService(Components.interfaces.nsIIOService).newFileURI(this.location);
|
||||
} catch(e) {}
|
||||
}
|
||||
|
||||
this._itemSaver = new Zotero.Translate.ItemSaver(this._libraryID,
|
||||
Zotero.Translate.ItemSaver[(this._saveAttachments ? "ATTACHMENT_MODE_FILE" : "ATTACHMENT_MODE_IGNORE")],
|
||||
undefined, undefined, undefined, baseURI);
|
||||
|
||||
this._itemSaver = new Zotero.Translate.ItemSaver({
|
||||
"libraryID":this._libraryID,
|
||||
"attachmentMode":Zotero.Translate.ItemSaver[(this._saveAttachments ? "ATTACHMENT_MODE_FILE" : "ATTACHMENT_MODE_IGNORE")],
|
||||
"baseURI":baseURI
|
||||
});
|
||||
this.newItems = [];
|
||||
this.newCollections = [];
|
||||
}
|
||||
|
@ -2215,7 +2297,7 @@ Zotero.Translate.Export.prototype.translate = function() {
|
|||
if(!this.translator || !this.translator.length) {
|
||||
this.complete(false, new Error("Export translation initiated without setting a translator"));
|
||||
} else {
|
||||
Zotero.Translate.Base.prototype.translate.apply(this, arguments);
|
||||
return Zotero.Translate.Base.prototype.translate.apply(this, arguments);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -23,52 +23,42 @@
|
|||
***** END LICENSE BLOCK *****
|
||||
*/
|
||||
|
||||
Zotero.Translate.ItemSaver = function(libraryID, attachmentMode, forceTagType, document,
|
||||
cookieSandbox, baseURI) {
|
||||
|
||||
/**
|
||||
* Save translator items
|
||||
* @constructor
|
||||
* @param {Object} options
|
||||
* <li>libraryID - ID of library in which items should be saved</li>
|
||||
* <li>attachmentMode - One of Zotero.Translate.ItemSaver.ATTACHMENT_* specifying how attachments should be saved</li>
|
||||
* <li>forceTagType - Force tags to specified tag type</li>
|
||||
* <li>cookieSandbox - Cookie sandbox for attachment requests</li>
|
||||
* <li>baseURI - URI to which attachment paths should be relative</li>
|
||||
*/
|
||||
Zotero.Translate.ItemSaver = function(options) {
|
||||
// initialize constants
|
||||
this.newItems = [];
|
||||
this.newCollections = [];
|
||||
this._IDMap = {};
|
||||
|
||||
// determine library ID
|
||||
if(libraryID === false) {
|
||||
this._libraryID = false;
|
||||
} else if(libraryID === true || libraryID == undefined) {
|
||||
this._libraryID = null;
|
||||
if(!options.libraryID) {
|
||||
this._libraryID = Zotero.Libraries.userLibraryID;
|
||||
} else {
|
||||
this._libraryID = libraryID;
|
||||
this._libraryID = options.libraryID;
|
||||
}
|
||||
|
||||
this.attachmentMode = attachmentMode;
|
||||
|
||||
// If group filesEditable==false, don't save attachments
|
||||
if (typeof this._libraryID == 'number') {
|
||||
var type = Zotero.Libraries.getType(this._libraryID);
|
||||
switch (type) {
|
||||
case 'group':
|
||||
var groupID = Zotero.Groups.getGroupIDFromLibraryID(this._libraryID);
|
||||
var group = Zotero.Groups.get(groupID);
|
||||
if (!group.filesEditable) {
|
||||
this.attachmentMode = Zotero.Translate.ItemSaver.ATTACHMENT_MODE_IGNORE;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// force tag types if requested
|
||||
this._forceTagType = forceTagType;
|
||||
// to set cookies on downloaded files
|
||||
this._cookieSandbox = cookieSandbox;
|
||||
this.attachmentMode = Zotero.Libraries.isFilesEditable(this._libraryID) ? options.attachmentMode :
|
||||
Zotero.Translate.ItemSaver.ATTACHMENT_MODE_IGNORE;
|
||||
this._forceTagType = options.forceTagType;
|
||||
this._cookieSandbox = options.cookieSandbox;
|
||||
|
||||
// the URI to which other URIs are assumed to be relative
|
||||
if(typeof baseURI === "object" && baseURI instanceof Components.interfaces.nsIURI) {
|
||||
this._baseURI = baseURI;
|
||||
this._baseURI = options.baseURI;
|
||||
} else {
|
||||
// try to convert to a URI
|
||||
this._baseURI = null;
|
||||
try {
|
||||
this._baseURI = Components.classes["@mozilla.org/network/io-service;1"].
|
||||
getService(Components.interfaces.nsIIOService).newURI(baseURI, null, null);
|
||||
getService(Components.interfaces.nsIIOService).newURI(options.baseURI, null, null);
|
||||
} catch(e) {};
|
||||
}
|
||||
};
|
||||
|
@ -83,130 +73,146 @@ Zotero.Translate.ItemSaver.prototype = {
|
|||
* @param items Items in Zotero.Item.toArray() format
|
||||
* @param {Function} callback A callback to be executed when saving is complete. If saving
|
||||
* succeeded, this callback will be passed true as the first argument and a list of items
|
||||
* saved as the second. If
|
||||
saving failed, the callback will be passed false as the first
|
||||
* saved as the second. If saving failed, the callback will be passed false as the first
|
||||
* argument and an error object as the second
|
||||
* @param {Function} [attachmentCallback] A callback that receives information about attachment
|
||||
* save progress. The callback will be called as attachmentCallback(attachment, false, error)
|
||||
* on failure or attachmentCallback(attachment, progressPercent) periodically during saving.
|
||||
*/
|
||||
"saveItems":function(items, callback, attachmentCallback) {
|
||||
Zotero.DB.executeTransaction(function* () {
|
||||
Zotero.spawn(function* () {
|
||||
try {
|
||||
var newItems = [];
|
||||
for each(var item in items) {
|
||||
// Get typeID, defaulting to "webpage"
|
||||
var newItem;
|
||||
var type = (item.itemType ? item.itemType : "webpage");
|
||||
|
||||
if(type == "note") { // handle notes differently
|
||||
newItem = new Zotero.Item('note');
|
||||
newItem.libraryID = this._libraryID;
|
||||
if(item.note) newItem.setNote(item.note);
|
||||
var myID = newItem.save();
|
||||
newItem = Zotero.Items.get(myID);
|
||||
} else {
|
||||
if(type == "attachment") { // handle attachments differently
|
||||
newItem = this._saveAttachment(item, null, attachmentCallback);
|
||||
if(!newItem) continue;
|
||||
var myID = newItem.id;
|
||||
let newItems = [], standaloneAttachments = [];
|
||||
yield (Zotero.DB.executeTransaction(function* () {
|
||||
for (let iitem=0; iitem<items.length; iitem++) {
|
||||
let item = items[iitem], newItem, myID;
|
||||
// Type defaults to "webpage"
|
||||
let type = (item.itemType ? item.itemType : "webpage");
|
||||
|
||||
if (type == "note") { // handle notes differently
|
||||
newItem = yield this._saveNote(item);
|
||||
} else if (type == "attachment") { // handle attachments differently
|
||||
standaloneAttachments.push(iitem);
|
||||
continue;
|
||||
} else {
|
||||
var typeID = Zotero.ItemTypes.getID(type);
|
||||
newItem = new Zotero.Item(typeID);
|
||||
newItem._libraryID = this._libraryID;
|
||||
newItem = new Zotero.Item(type);
|
||||
newItem.libraryID = this._libraryID;
|
||||
if(item.tags) item.tags = this._cleanTags(item.tags);
|
||||
|
||||
this._saveFields(item, newItem);
|
||||
|
||||
// handle creators
|
||||
if(item.creators) {
|
||||
newItem.setCreators(item.creators);
|
||||
}
|
||||
// Need to handle these specially. Put them in a separate object to
|
||||
// avoid a warning from fromJSON()
|
||||
let specialFields = {
|
||||
attachments:item.attachments,
|
||||
notes:item.notes,
|
||||
seeAlso:item.seeAlso,
|
||||
id:item.itemID || item.id
|
||||
};
|
||||
if (item.version) item.versionNumber = item.version;
|
||||
newItem.fromJSON(this._deleteIrrelevantFields(item));
|
||||
|
||||
// save item
|
||||
var myID = yield newItem.save();
|
||||
newItem = yield Zotero.Items.getAsync(myID);
|
||||
myID = yield newItem.save();
|
||||
|
||||
// handle notes
|
||||
if(item.notes) {
|
||||
this._saveNotes(item, myID);
|
||||
if (specialFields.notes) {
|
||||
for (let i=0; i<specialFields.notes.length; i++) {
|
||||
yield this._saveNote(specialFields.notes[i], myID);
|
||||
}
|
||||
}
|
||||
|
||||
// handle attachments
|
||||
if(item.attachments) {
|
||||
for(var i=0; i<item.attachments.length; i++) {
|
||||
var newAttachment = this._saveAttachment(item.attachments[i], myID, attachmentCallback);
|
||||
if(typeof newAttachment === "object") {
|
||||
this._saveTags(item.attachments[i], newAttachment);
|
||||
}
|
||||
if (specialFields.attachments) {
|
||||
for (let i=0; i<specialFields.attachments.length; i++) {
|
||||
let attachment = specialFields.attachments[i];
|
||||
// Don't wait for the promise to resolve, since we want to
|
||||
// signal completion as soon as the items are saved
|
||||
this._saveAttachment(attachment, myID, attachmentCallback);
|
||||
}
|
||||
// Restore the attachments field, since we use it later in
|
||||
// translation
|
||||
item.attachments = specialFields.attachments;
|
||||
}
|
||||
|
||||
// handle see also
|
||||
this._handleRelated(specialFields, newItem);
|
||||
}
|
||||
|
||||
// add to new item list
|
||||
newItems.push(newItem);
|
||||
}
|
||||
}.bind(this)));
|
||||
|
||||
if(item.itemID) this._IDMap[item.itemID] = myID;
|
||||
|
||||
// handle see also
|
||||
this._saveTags(item, newItem);
|
||||
|
||||
// add to new item list
|
||||
newItem = Zotero.Items.get(myID);
|
||||
newItems.push(newItem);
|
||||
// Handle standalone attachments outside of the transaction
|
||||
for (let iitem of standaloneAttachments) {
|
||||
let newItem = yield this._saveAttachment(items[iitem], null, attachmentCallback);
|
||||
if (newItem) newItems.push(newItem);
|
||||
}
|
||||
|
||||
|
||||
callback(true, newItems);
|
||||
} catch(e) {
|
||||
callback(false, e);
|
||||
}
|
||||
}.bind(this));
|
||||
}, this);
|
||||
},
|
||||
|
||||
"saveCollection": Zotero.Promise.coroutine(function* (collection) {
|
||||
var collectionsToProcess = [collection];
|
||||
"saveCollections": Zotero.Promise.coroutine(function* (collections) {
|
||||
var collectionsToProcess = collections.slice();
|
||||
var parentIDs = [null];
|
||||
var topLevelCollection;
|
||||
|
||||
while(collectionsToProcess.length) {
|
||||
var collection = collectionsToProcess.shift();
|
||||
var parentID = parentIDs.shift();
|
||||
|
||||
var newCollection = new Zotero.Collection;
|
||||
newCollection.libraryID = this._libraryID;
|
||||
newCollection.name = collection.name;
|
||||
if (parentID) {
|
||||
newCollection.parentID = parentID;
|
||||
}
|
||||
yield newCollection.save();
|
||||
|
||||
if(parentID === null) topLevelCollection = newCollection;
|
||||
|
||||
this.newCollections.push(newCollection.id);
|
||||
|
||||
var toAdd = [];
|
||||
|
||||
for(var i=0; i<collection.children.length; i++) {
|
||||
var child = collection.children[i];
|
||||
if(child.type === "collection") {
|
||||
// do recursive processing of collections
|
||||
collectionsToProcess.push(child);
|
||||
parentIDs.push(newCollection.id);
|
||||
} else {
|
||||
// add mapped items to collection
|
||||
if(this._IDMap[child.id]) {
|
||||
toAdd.push(this._IDMap[child.id]);
|
||||
var topLevelCollections = [];
|
||||
|
||||
yield Zotero.DB.executeTransaction(function* () {
|
||||
while(collectionsToProcess.length) {
|
||||
var collection = collectionsToProcess.shift();
|
||||
var parentID = parentIDs.shift();
|
||||
|
||||
var newCollection = new Zotero.Collection;
|
||||
newCollection.libraryID = this._libraryID;
|
||||
newCollection.name = collection.name;
|
||||
if (parentID) {
|
||||
newCollection.parentID = parentID;
|
||||
}
|
||||
yield newCollection.save();
|
||||
|
||||
if(parentID === null) topLevelCollections.push(newCollection);
|
||||
|
||||
var toAdd = [];
|
||||
|
||||
for(var i=0; i<collection.children.length; i++) {
|
||||
var child = collection.children[i];
|
||||
if(child.type === "collection") {
|
||||
// do recursive processing of collections
|
||||
collectionsToProcess.push(child);
|
||||
parentIDs.push(newCollection.id);
|
||||
} else {
|
||||
Zotero.debug("Translate: Could not map "+child.id+" to an imported item", 2);
|
||||
// add mapped items to collection
|
||||
if(this._IDMap[child.id]) {
|
||||
toAdd.push(this._IDMap[child.id]);
|
||||
} else {
|
||||
Zotero.debug("Translate: Could not map "+child.id+" to an imported item", 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(toAdd.length) {
|
||||
Zotero.debug("Translate: Adding " + toAdd, 5);
|
||||
yield newCollection.addItems(toAdd);
|
||||
}
|
||||
}
|
||||
|
||||
if(toAdd.length) {
|
||||
Zotero.debug("Translate: Adding " + toAdd, 5);
|
||||
yield newCollection.addItems(toAdd);
|
||||
}
|
||||
}
|
||||
|
||||
return topLevelCollection;
|
||||
}.bind(this));
|
||||
|
||||
return topLevelCollections;
|
||||
}),
|
||||
|
||||
/**
|
||||
* Deletes irrelevant fields from an item object to avoid warnings in Item#fromJSON
|
||||
* Also delete some things like dateAdded, dateModified, and path that translators
|
||||
* should not be able to set directly.
|
||||
*/
|
||||
"_deleteIrrelevantFields": function(item) {
|
||||
const DELETE_FIELDS = ["attachments", "notes", "dateAdded", "dateModified", "seeAlso", "version", "id", "itemID", "path"];
|
||||
for (let i=0; i<DELETE_FIELDS.length; i++) delete item[DELETE_FIELDS[i]];
|
||||
return item;
|
||||
},
|
||||
|
||||
/**
|
||||
* Saves a translator attachment to the database
|
||||
|
@ -220,44 +226,38 @@ Zotero.Translate.ItemSaver.prototype = {
|
|||
* @return {Zotero.Primise<Zotero.Item|False} Flase is returned if attachment
|
||||
* was not saved due to error or user settings.
|
||||
*/
|
||||
"_saveAttachment": function(attachment, parentID, attachmentCallback) {
|
||||
// determine whether to save files and attachments
|
||||
let attachmentPromise;
|
||||
if (this.attachmentMode == Zotero.Translate.ItemSaver.ATTACHMENT_MODE_DOWNLOAD) {
|
||||
attachmentPromise = this._saveAttachmentDownload.apply(this, arguments);
|
||||
} else if (this.attachmentMode == Zotero.Translate.ItemSaver.ATTACHMENT_MODE_FILE) {
|
||||
attachmentPromise = this._saveAttachmentFile.apply(this, arguments);
|
||||
} else {
|
||||
Zotero.debug('Translate: Ignoring attachment due to ATTACHMENT_MODE_IGNORE');
|
||||
return Zotero.Promise.resolve(false);
|
||||
}
|
||||
|
||||
return attachmentPromise
|
||||
.then(function(attachmentItem) {
|
||||
if (!attachmentItem) return false; // attachmentCallback should not have been called in this case
|
||||
|
||||
// save fields
|
||||
attachment.itemType = "attachment";
|
||||
this._saveFields(attachment, attachmentItem);
|
||||
|
||||
// add note if necessary
|
||||
if(attachment.note) {
|
||||
attachmentItem.setNote(attachment.note);
|
||||
"_saveAttachment": Zotero.Promise.coroutine(function* (attachment, parentID, attachmentCallback) {
|
||||
try {
|
||||
let newAttachment;
|
||||
|
||||
// determine whether to save files and attachments
|
||||
if (this.attachmentMode == Zotero.Translate.ItemSaver.ATTACHMENT_MODE_DOWNLOAD) {
|
||||
newAttachment = yield this._saveAttachmentDownload.apply(this, arguments);
|
||||
} else if (this.attachmentMode == Zotero.Translate.ItemSaver.ATTACHMENT_MODE_FILE) {
|
||||
newAttachment = yield this._saveAttachmentFile.apply(this, arguments);
|
||||
} else {
|
||||
Zotero.debug('Translate: Ignoring attachment due to ATTACHMENT_MODE_IGNORE');
|
||||
return false;
|
||||
}
|
||||
|
||||
return attachmentItem.save()
|
||||
.then(function() {
|
||||
Zotero.debug("Translate: Created attachment; id is " + attachmentItem.id, 4);
|
||||
attachmentCallback(attachment, 100);
|
||||
return attachmentItem;
|
||||
})
|
||||
}.bind(this))
|
||||
.catch(function(e) {
|
||||
if (!newAttachment) return false; // attachmentCallback should not have been called in this case
|
||||
|
||||
// save fields
|
||||
if (attachment.accessDate) newAttachment.setField("accessDate", attachment.accessDate);
|
||||
if (attachment.tags) newAttachment.setTags(this._cleanTags(attachment.tags));
|
||||
if (attachment.note) newAttachment.setNote(attachment.note);
|
||||
this._handleRelated(attachment, newAttachment);
|
||||
yield newAttachment.saveTx();
|
||||
|
||||
Zotero.debug("Translate: Created attachment; id is " + newAttachment.id, 4);
|
||||
attachmentCallback(attachment, 100);
|
||||
return newAttachment;
|
||||
} catch(e) {
|
||||
Zotero.debug(e, 2);
|
||||
attachmentCallback(attachment, false, e);
|
||||
return false;
|
||||
});
|
||||
},
|
||||
}
|
||||
}),
|
||||
|
||||
"_saveAttachmentFile": Zotero.Promise.coroutine(function* (attachment, parentID, attachmentCallback) {
|
||||
Zotero.debug("Translate: Adding attachment", 4);
|
||||
|
@ -278,60 +278,39 @@ Zotero.Translate.ItemSaver.prototype = {
|
|||
}
|
||||
}
|
||||
|
||||
let done = false,
|
||||
newItem;
|
||||
if (attachment.path) {
|
||||
var file = this._parsePath(attachment.path);
|
||||
if(!file) {
|
||||
let newItem;
|
||||
var file = attachment.path && this._parsePath(attachment.path);
|
||||
if (!file) {
|
||||
if (attachment.path) {
|
||||
let asUrl = Zotero.Attachments.cleanAttachmentURI(attachment.path);
|
||||
if (!attachment.url && !asUrl) {
|
||||
throw new Error("Translate: Could not parse attachment path <" + attachment.path + ">");
|
||||
} else if (!attachment.url && asUrl) {
|
||||
}
|
||||
|
||||
if (!attachment.url && asUrl) {
|
||||
Zotero.debug("Translate: attachment path looks like a URI: " + attachment.path);
|
||||
attachment.url = asUrl;
|
||||
delete attachment.path;
|
||||
}
|
||||
} else {
|
||||
if (attachment.url) {
|
||||
attachment.linkMode = "imported_url";
|
||||
newItem = yield Zotero.Attachments.importSnapshotFromFile({
|
||||
file: file,
|
||||
url: attachment.url,
|
||||
title: attachment.title,
|
||||
contentType: attachment.mimeType,
|
||||
charset: attachment.charset,
|
||||
parentItemID: parentID
|
||||
});
|
||||
}
|
||||
else {
|
||||
attachment.linkMode = "imported_file";
|
||||
newItem = yield Zotero.Attachments.importFromFile({
|
||||
file: file,
|
||||
parentItemID: parentID
|
||||
});
|
||||
}
|
||||
done = true;
|
||||
}
|
||||
}
|
||||
|
||||
if(!done) {
|
||||
|
||||
let url = Zotero.Attachments.cleanAttachmentURI(attachment.url);
|
||||
if (!url) {
|
||||
throw new Error("Translate: Invalid attachment.url specified <" + attachment.url + ">");
|
||||
}
|
||||
|
||||
|
||||
attachment.url = url;
|
||||
url = Components.classes["@mozilla.org/network/io-service;1"]
|
||||
.getService(Components.interfaces.nsIIOService)
|
||||
.newURI(url, null, null); // This cannot fail, since we check above
|
||||
|
||||
|
||||
// see if this is actually a file URL
|
||||
if(url.scheme == "file") {
|
||||
throw new Error("Translate: Local file attachments cannot be specified in attachment.url");
|
||||
} else if(url.scheme != "http" && url.scheme != "https") {
|
||||
throw new Error("Translate: " + url.scheme + " protocol is not allowed for attachments from translators.");
|
||||
}
|
||||
|
||||
|
||||
// At this point, must be a valid HTTP/HTTPS url
|
||||
attachment.linkMode = "linked_file";
|
||||
newItem = yield Zotero.Attachments.linkFromURL({
|
||||
|
@ -339,7 +318,27 @@ Zotero.Translate.ItemSaver.prototype = {
|
|||
parentItemID: parentID,
|
||||
contentType: attachment.mimeType || undefined,
|
||||
title: attachment.title || undefined
|
||||
})
|
||||
});
|
||||
} else {
|
||||
if (attachment.url) {
|
||||
attachment.linkMode = "imported_url";
|
||||
newItem = yield Zotero.Attachments.importSnapshotFromFile({
|
||||
file: file,
|
||||
url: attachment.url,
|
||||
title: attachment.title,
|
||||
contentType: attachment.mimeType,
|
||||
charset: attachment.charset,
|
||||
parentItemID: parentID
|
||||
});
|
||||
}
|
||||
else {
|
||||
attachment.linkMode = "imported_file";
|
||||
newItem = yield Zotero.Attachments.importFromFile({
|
||||
file: file,
|
||||
parentItemID: parentID
|
||||
});
|
||||
if (attachment.title) newItem.setField("title", attachment.title);
|
||||
}
|
||||
}
|
||||
|
||||
return newItem;
|
||||
|
@ -562,8 +561,11 @@ Zotero.Translate.ItemSaver.prototype = {
|
|||
|
||||
// Import from URL
|
||||
let mimeType = attachment.mimeType ? attachment.mimeType : null;
|
||||
let parentItem = yield Zotero.Items.getAsync(parentID);
|
||||
let fileBaseName = Zotero.Attachments.getFileBaseNameFromItem(parentItem);
|
||||
let fileBaseName;
|
||||
if (parentID) {
|
||||
let parentItem = yield Zotero.Items.getAsync(parentID);
|
||||
fileBaseName = Zotero.Attachments.getFileBaseNameFromItem(parentItem);
|
||||
}
|
||||
|
||||
Zotero.debug('Importing attachment from URL');
|
||||
attachment.linkMode = "imported_url";
|
||||
|
@ -578,125 +580,61 @@ Zotero.Translate.ItemSaver.prototype = {
|
|||
fileBaseName: fileBaseName,
|
||||
contentType: mimeType,
|
||||
cookieSandbox: this._cookieSandbox
|
||||
})
|
||||
});
|
||||
}),
|
||||
|
||||
"_saveFields":function(item, newItem) {
|
||||
// fields that should be handled differently
|
||||
const skipFields = ["note", "notes", "itemID", "attachments", "tags", "seeAlso",
|
||||
"itemType", "complete", "creators"];
|
||||
|
||||
var typeID = Zotero.ItemTypes.getID(item.itemType);
|
||||
var fieldID;
|
||||
for(var field in item) {
|
||||
// loop through item fields
|
||||
if(item[field] && skipFields.indexOf(field) === -1 && (fieldID = Zotero.ItemFields.getID(field))) {
|
||||
// if field is in db and shouldn't be skipped
|
||||
|
||||
// try to map from base field
|
||||
if(Zotero.ItemFields.isBaseField(fieldID)) {
|
||||
fieldID = Zotero.ItemFields.getFieldIDFromTypeAndBase(typeID, fieldID);
|
||||
|
||||
// Skip mapping if item field already exists
|
||||
var fieldName = Zotero.ItemFields.getName(fieldID);
|
||||
if(fieldName !== field && item[fieldName]) continue;
|
||||
|
||||
if(fieldID) {
|
||||
Zotero.debug("Translate: Mapping "+field+" to "+fieldName, 5);
|
||||
}
|
||||
}
|
||||
|
||||
// if field is valid for this type, set field
|
||||
if(fieldID && Zotero.ItemFields.isValidForType(fieldID, typeID)) {
|
||||
newItem.setField(fieldID, item[field]);
|
||||
} else {
|
||||
Zotero.debug("Translate: Discarded field "+field+" for item: field not valid for type "+item.itemType, 3);
|
||||
}
|
||||
}
|
||||
"_saveNote":Zotero.Promise.coroutine(function* (note, parentID) {
|
||||
var myNote = new Zotero.Item('note');
|
||||
myNote.libraryID = this._libraryID;
|
||||
if(parentID) {
|
||||
myNote.parentID = parentID;
|
||||
}
|
||||
|
||||
if(typeof note == "object") {
|
||||
myNote.setNote(note.note);
|
||||
if(note.tags) myNote.setTags(this._cleanTags(note.tags));
|
||||
this._handleRelated(note, myNote);
|
||||
} else {
|
||||
myNote.setNote(note);
|
||||
}
|
||||
yield myNote.save();
|
||||
return myNote;
|
||||
}),
|
||||
|
||||
/**
|
||||
* Remove automatic tags if automatic tags pref is on, and set type
|
||||
* to automatic if forced
|
||||
*/
|
||||
"_cleanTags":function(tags) {
|
||||
// If all tags are automatic and automatic tags pref is on, return immediately
|
||||
let tagPref = Zotero.Prefs.get("automaticTags");
|
||||
if(this._forceTagType == 1 && !tagPref) return [];
|
||||
|
||||
let newTags = [];
|
||||
for(let i=0; i<tags.length; i++) {
|
||||
let tag = tags[i];
|
||||
tag.type = this._forceTagType || tag.type || 0;
|
||||
newTags.push(tag);
|
||||
}
|
||||
return newTags;
|
||||
},
|
||||
|
||||
"_saveNotes":function(item, parentID) {
|
||||
for(var i=0; i<item.notes.length; i++) {
|
||||
var note = item.notes[i];
|
||||
if(!note) continue;
|
||||
var myNote = new Zotero.Item('note');
|
||||
myNote.libraryID = this._libraryID;
|
||||
myNote.setNote(typeof note == "object" ? note.note : note);
|
||||
if(parentID) {
|
||||
myNote.parentID = parentID;
|
||||
}
|
||||
var noteID = myNote.save();
|
||||
|
||||
if(typeof note == "object") {
|
||||
// handle see also
|
||||
myNote = Zotero.Items.get(noteID);
|
||||
this._saveTags(note, myNote);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"_saveTags":function(item, newItem) {
|
||||
"_handleRelated":function(item, newItem) {
|
||||
// add to ID map
|
||||
if(item.itemID) {
|
||||
this._IDMap[item.itemID] = newItem.id;
|
||||
}
|
||||
|
||||
// add see alsos
|
||||
if(item.seeAlso) {
|
||||
for(var i=0; i<item.seeAlso.length; i++) {
|
||||
var seeAlso = item.seeAlso[i];
|
||||
if(this._IDMap[seeAlso]) {
|
||||
newItem.addRelatedItem(this._IDMap[seeAlso]);
|
||||
}
|
||||
}
|
||||
newItem.save();
|
||||
}
|
||||
|
||||
// if all tags are automatic and automatic tags pref is on, return immediately
|
||||
var tagPref = Zotero.Prefs.get("automaticTags");
|
||||
if(this._forceTagType == 1 && !tagPref) return;
|
||||
|
||||
// add tags
|
||||
if(item.tags) {
|
||||
var tagsToAdd = {};
|
||||
tagsToAdd[0] = []; // user tags
|
||||
tagsToAdd[1] = []; // automatic tags
|
||||
|
||||
for(var i=0; i<item.tags.length; i++) {
|
||||
var tag = item.tags[i];
|
||||
|
||||
if(typeof(tag) == "string") {
|
||||
// accept strings in tag array as automatic tags, or, if
|
||||
// importing, as non-automatic tags
|
||||
if(this._forceTagType) {
|
||||
tagsToAdd[this._forceTagType].push(tag);
|
||||
} else {
|
||||
tagsToAdd[0].push(tag);
|
||||
}
|
||||
} else if(typeof(tag) == "object") {
|
||||
// also accept objects
|
||||
if(tag.tag || tag.name) {
|
||||
if(this._forceTagType) {
|
||||
var tagType = this._forceTagType;
|
||||
} else if(tag.type) {
|
||||
// skip automatic tags during import too (?)
|
||||
if(tag.type == 1 && !tagPref) continue;
|
||||
var tagType = tag.type;
|
||||
} else {
|
||||
var tagType = 0;
|
||||
}
|
||||
tagsToAdd[tagType].push(tag.tag ? tag.tag : tag.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (var type in [0, 1]) {
|
||||
if (tagsToAdd[type].length) {
|
||||
newItem.addTags(tagsToAdd[type], type);
|
||||
}
|
||||
}
|
||||
if(item.itemID || item.id) {
|
||||
this._IDMap[item.itemID || item.id] = newItem.id;
|
||||
}
|
||||
|
||||
// // add see alsos
|
||||
// if(item.seeAlso) {
|
||||
// for(var i=0; i<item.seeAlso.length; i++) {
|
||||
// var seeAlso = item.seeAlso[i];
|
||||
// if(this._IDMap[seeAlso]) {
|
||||
// newItem.addRelatedItem(this._IDMap[seeAlso]);
|
||||
// }
|
||||
// }
|
||||
// newItem.save();
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -863,7 +801,7 @@ Zotero.Translate.ItemGetter.prototype = {
|
|||
|
||||
// The only attachments that can have multiple supporting files are imported
|
||||
// attachments of mime type text/html (specified in Attachments.getNumFiles())
|
||||
if(attachment.attachmentMIMEType == "text/html"
|
||||
if(attachment.attachmentContentType == "text/html"
|
||||
&& linkMode != Zotero.Attachments.LINK_MODE_LINKED_FILE
|
||||
&& Zotero.Attachments.getNumFiles(attachment) > 1) {
|
||||
// Attachment is a snapshot with supporting files. Check if any of the
|
||||
|
|
|
@ -105,6 +105,7 @@ user_pref("extensions.zotero.debug.time", $DEBUG);
|
|||
user_pref("extensions.zotero.firstRunGuidance", false);
|
||||
user_pref("extensions.zotero.firstRun2", false);
|
||||
user_pref("extensions.zotero.reportTranslationFailure", false);
|
||||
user_pref("extensions.zotero.httpServer.enabled", true);
|
||||
EOF
|
||||
|
||||
# -v flag on Windows makes Firefox process hang
|
||||
|
|
16
test/tests/data/snapshot/index.html
Normal file
16
test/tests/data/snapshot/index.html
Normal file
|
@ -0,0 +1,16 @@
|
|||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=windows-1252">
|
||||
|
||||
|
||||
<meta name="wpd_version" content="0.2">
|
||||
<meta name="wpd_baseurl" content="http://209.141.35.188/">
|
||||
<meta name="wpd_url" content="http://209.141.35.188/">
|
||||
<meta name="wpd_date" content="2015-05-05T00:06Z">
|
||||
</head>
|
||||
<body><h1>It works!</h1>
|
||||
<p>This is the default web page for this server.</p>
|
||||
<p>The web server software is running but no content has been added, yet.</p>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -1,5 +1,551 @@
|
|||
new function() {
|
||||
Components.utils.import("resource://gre/modules/osfile.jsm");
|
||||
|
||||
/**
|
||||
* Build a dummy translator that can be passed to Zotero.Translate
|
||||
*/
|
||||
function buildDummyTranslator(translatorType, code) {
|
||||
let info = {
|
||||
"translatorID":"dummy-translator",
|
||||
"translatorType":1, // import
|
||||
"label":"Dummy Translator",
|
||||
"creator":"Simon Kornblith",
|
||||
"target":"",
|
||||
"priority":100,
|
||||
"browserSupport":"g",
|
||||
"inRepository":false,
|
||||
"lastUpdated":"0000-00-00 00:00:00",
|
||||
};
|
||||
let translator = new Zotero.Translator(info);
|
||||
translator.code = code;
|
||||
return translator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new translator that saves the specified items
|
||||
* @param {String} translatorType - "import" or "web"
|
||||
* @param {Object} items - items as translator JSON
|
||||
*/
|
||||
function saveItemsThroughTranslator(translatorType, items) {
|
||||
let tyname;
|
||||
if (translatorType == "web") {
|
||||
tyname = "Web";
|
||||
} else if (translatorType == "import") {
|
||||
tyname = "Import";
|
||||
} else {
|
||||
throw new Error("invalid translator type "+translatorType);
|
||||
}
|
||||
|
||||
let translate = new Zotero.Translate[tyname]();
|
||||
let browser;
|
||||
if (translatorType == "web") {
|
||||
browser = Zotero.Browser.createHiddenBrowser();
|
||||
translate.setDocument(browser.contentDocument);
|
||||
} else if (translatorType == "import") {
|
||||
translate.setString("");
|
||||
}
|
||||
translate.setTranslator(buildDummyTranslator(translatorType == "web" ? 4 : 1,
|
||||
"function detectWeb() {}\n"+
|
||||
"function do"+tyname+"() {\n"+
|
||||
" var json = JSON.parse('"+JSON.stringify(items).replace(/'/g, "\'")+"');\n"+
|
||||
" for (var i=0; i<json.length; i++) {"+
|
||||
" var item = new Zotero.Item;\n"+
|
||||
" for (var field in json[i]) { item[field] = json[i][field]; }\n"+
|
||||
" item.complete();\n"+
|
||||
" }\n"+
|
||||
"}"));
|
||||
return translate.translate().then(function(items) {
|
||||
if (browser) Zotero.Browser.deleteHiddenBrowser(browser);
|
||||
return items;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an array of items to an object in which they are indexed by
|
||||
* their display titles
|
||||
*/
|
||||
var itemsArrayToObject = Zotero.Promise.coroutine(function* itemsArrayToObject(items) {
|
||||
var obj = {};
|
||||
for (let item of items) {
|
||||
obj[yield item.loadDisplayTitle(true)] = item;
|
||||
}
|
||||
return obj;
|
||||
});
|
||||
|
||||
const TEST_TAGS = [
|
||||
"manual tag as string",
|
||||
{"tag":"manual tag as object"},
|
||||
{"tag":"manual tag as object with type", "type":0},
|
||||
{"tag":"automatic tag as object", "type":1},
|
||||
{"name":"tag in name property"}
|
||||
];
|
||||
|
||||
/**
|
||||
* Check that tags match expected values, if TEST_TAGS is passed as test array
|
||||
*/
|
||||
function checkTestTags(newItem, web) {
|
||||
assert.equal(newItem.getTagType("manual tag as string"), web ? 1 : 0);
|
||||
assert.equal(newItem.getTagType("manual tag as object"), web ? 1 : 0);
|
||||
assert.equal(newItem.getTagType("manual tag as object with type"), web ? 1 : 0);
|
||||
assert.equal(newItem.getTagType("automatic tag as object"), 1);
|
||||
assert.equal(newItem.getTagType("tag in name property"), web ? 1 : 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get included test snapshot file
|
||||
* @returns {nsIFile}
|
||||
*/
|
||||
function getTestSnapshot() {
|
||||
let snapshot = getTestDataDirectory();
|
||||
snapshot.append("snapshot");
|
||||
snapshot.append("index.html");
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get included test snapshot file
|
||||
* @returns {nsIFile}
|
||||
*/
|
||||
function getTestPDF() {
|
||||
let testPDF = getTestDataDirectory();
|
||||
testPDF.append("empty.pdf");
|
||||
return testPDF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up endpoints for testing attachment saving
|
||||
* This must happen immediately before the test, since Zotero might get
|
||||
* restarted by resetDB(), which would erase our registered endpoints.
|
||||
*/
|
||||
function setupAttachmentEndpoints() {
|
||||
var SnapshotTest = function() {};
|
||||
Zotero.Server.Endpoints["/test/translate/test.html"] = SnapshotTest;
|
||||
SnapshotTest.prototype = {
|
||||
"supportedMethods":["GET"],
|
||||
"init":function(data, sendResponseCallback) {
|
||||
Zotero.File.getBinaryContentsAsync(getTestSnapshot()).then(function (data) {
|
||||
sendResponseCallback(200, "text/html", data);
|
||||
});
|
||||
}
|
||||
}
|
||||
var PDFTest = function() {};
|
||||
Zotero.Server.Endpoints["/test/translate/test.pdf"] = PDFTest;
|
||||
PDFTest.prototype = {
|
||||
"supportedMethods":["GET"],
|
||||
"init":function(data, sendResponseCallback) {
|
||||
Zotero.File.getBinaryContentsAsync(getTestPDF()).then(function (data) {
|
||||
sendResponseCallback(200, "application/pdf", data);
|
||||
});
|
||||
}
|
||||
}
|
||||
var NonExistentTest = function() {};
|
||||
Zotero.Server.Endpoints["/test/translate/does_not_exist.html"] = NonExistentTest;
|
||||
NonExistentTest.prototype = {
|
||||
"supportedMethods":["GET"],
|
||||
"init":function(data, sendResponseCallback) {
|
||||
sendResponseCallback(404, "text/html", "File does not exist");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
describe("Zotero.Translate", function() {
|
||||
let win;
|
||||
before(function* () {
|
||||
setupAttachmentEndpoints();
|
||||
win = yield loadBrowserWindow();
|
||||
});
|
||||
after(function () {
|
||||
win.close();
|
||||
});
|
||||
|
||||
describe("Zotero.Item", function() {
|
||||
it('should save ordinary fields and creators', function* () {
|
||||
let data = loadSampleData('allTypesAndFields');
|
||||
let trueItems = loadSampleData('itemJSON');
|
||||
let saveItems = [];
|
||||
for (let itemType in data) {
|
||||
saveItems.push(data[itemType]);
|
||||
let trueItem = trueItems[itemType];
|
||||
delete trueItem.dateAdded;
|
||||
delete trueItem.dateModified;
|
||||
delete trueItem.key;
|
||||
}
|
||||
|
||||
let newItems = yield saveItemsThroughTranslator("import", saveItems);
|
||||
let savedItems = {};
|
||||
for (let i=0; i<newItems.length; i++) {
|
||||
let savedItem = yield newItems[i].toJSON();
|
||||
savedItems[Zotero.ItemTypes.getName(newItems[i].itemTypeID)] = savedItem;
|
||||
delete savedItem.dateAdded;
|
||||
delete savedItem.dateModified;
|
||||
delete savedItem.key;
|
||||
}
|
||||
assert.deepEqual(savedItems, trueItems, "saved items match inputs");
|
||||
});
|
||||
|
||||
it('should save tags', function* () {
|
||||
let myItem = {
|
||||
"itemType":"book",
|
||||
"title":"Test Item",
|
||||
"tags":TEST_TAGS
|
||||
};
|
||||
checkTestTags((yield saveItemsThroughTranslator("import", [myItem]))[0]);
|
||||
});
|
||||
|
||||
it('should save notes', function* () {
|
||||
let myItems = [
|
||||
{
|
||||
"itemType":"book",
|
||||
"title":"Test Item",
|
||||
"notes":[
|
||||
"1 note as string",
|
||||
{
|
||||
"note":"2 note as object",
|
||||
"tags":TEST_TAGS
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"itemType":"note",
|
||||
"note":"standalone note",
|
||||
"tags":TEST_TAGS
|
||||
}
|
||||
];
|
||||
|
||||
let newItems = yield itemsArrayToObject(yield saveItemsThroughTranslator("import", myItems));
|
||||
let noteIDs = newItems["Test Item"].getNotes();
|
||||
let note1 = yield Zotero.Items.getAsync(noteIDs[0]);
|
||||
assert.equal(Zotero.ItemTypes.getName(note1.itemTypeID), "note");
|
||||
assert.equal(note1.getNote(), "1 note as string");
|
||||
let note2 = yield Zotero.Items.getAsync(noteIDs[1]);
|
||||
assert.equal(Zotero.ItemTypes.getName(note2.itemTypeID), "note");
|
||||
assert.equal(note2.getNote(), "2 note as object");
|
||||
checkTestTags(note2);
|
||||
let note3 = newItems["standalone note"];
|
||||
assert.equal(note3.getNote(), "standalone note");
|
||||
checkTestTags(note3);
|
||||
});
|
||||
|
||||
it('should save collections', function* () {
|
||||
let translate = new Zotero.Translate.Import();
|
||||
translate.setString("");
|
||||
translate.setTranslator(buildDummyTranslator(4,
|
||||
'function detectWeb() {}\n'+
|
||||
'function doImport() {\n'+
|
||||
' var item1 = new Zotero.Item("book");\n'+
|
||||
' item1.title = "Not in Collection";\n'+
|
||||
' item1.complete();\n'+
|
||||
' var item2 = new Zotero.Item("book");\n'+
|
||||
' item2.id = 1;\n'+
|
||||
' item2.title = "In Parent Collection";\n'+
|
||||
' item2.complete();\n'+
|
||||
' var item3 = new Zotero.Item("book");\n'+
|
||||
' item3.id = 2;\n'+
|
||||
' item3.title = "In Child Collection";\n'+
|
||||
' item3.complete();\n'+
|
||||
' var collection = new Zotero.Collection();\n'+
|
||||
' collection.name = "Parent Collection";\n'+
|
||||
' collection.children = [{"id":1}, {"type":"collection", "name":"Child Collection", "children":[{"id":2}]}];\n'+
|
||||
' collection.complete();\n'+
|
||||
'}'));
|
||||
let newItems = yield translate.translate();
|
||||
assert.equal(newItems.length, 3);
|
||||
newItems = yield itemsArrayToObject(newItems);
|
||||
assert.equal(newItems["Not in Collection"].getCollections().length, 0);
|
||||
|
||||
let parentCollection = newItems["In Parent Collection"].getCollections();
|
||||
assert.equal(parentCollection.length, 1);
|
||||
parentCollection = (yield Zotero.Collections.getAsync(parentCollection))[0];
|
||||
assert.equal(parentCollection.name, "Parent Collection");
|
||||
assert.isTrue(parentCollection.hasChildCollections());
|
||||
|
||||
let childCollection = newItems["In Child Collection"].getCollections();
|
||||
assert.equal(childCollection.length, 1);
|
||||
childCollection = (yield Zotero.Collections.getAsync(childCollection[0]));
|
||||
assert.equal(childCollection.name, "Child Collection");
|
||||
let parentChildren = parentCollection.getChildCollections();
|
||||
assert.equal(parentChildren.length, 1);
|
||||
assert.equal(parentChildren[0], childCollection);
|
||||
});
|
||||
|
||||
it('import translators should save attachments', function* () {
|
||||
let emptyPDF = getTestPDF().path;
|
||||
let snapshot = getTestSnapshot().path;
|
||||
let myItems = [
|
||||
{
|
||||
"itemType":"attachment",
|
||||
"path":emptyPDF,
|
||||
"title":"Empty PDF",
|
||||
"note":"attachment note",
|
||||
"tags":TEST_TAGS
|
||||
},
|
||||
{
|
||||
"itemType":"attachment",
|
||||
"url":"http://www.zotero.org/",
|
||||
"title":"Link to zotero.org",
|
||||
"note":"attachment 2 note",
|
||||
"tags":TEST_TAGS
|
||||
}
|
||||
];
|
||||
let childAttachments = myItems.slice();
|
||||
childAttachments.push({
|
||||
"itemType":"attachment",
|
||||
"path":snapshot,
|
||||
"url":"http://www.example.com/",
|
||||
"title":"Snapshot",
|
||||
"note":"attachment 3 note",
|
||||
"tags":TEST_TAGS
|
||||
});
|
||||
myItems.push({
|
||||
"itemType":"book",
|
||||
"title":"Container Item",
|
||||
"attachments":childAttachments
|
||||
});
|
||||
|
||||
let newItems = yield itemsArrayToObject(yield saveItemsThroughTranslator("import", myItems));
|
||||
let containedAttachments = yield Zotero.Items.getAsync(newItems["Container Item"].getAttachments());
|
||||
assert.equal(containedAttachments.length, 3);
|
||||
|
||||
for (let savedAttachments of [[newItems["Empty PDF"], newItems["Link to zotero.org"]],
|
||||
[containedAttachments[0], containedAttachments[1]]]) {
|
||||
assert.equal(savedAttachments[0].getField("title"), "Empty PDF");
|
||||
assert.equal(savedAttachments[0].getNote(), "attachment note");
|
||||
assert.equal(savedAttachments[0].attachmentLinkMode, Zotero.Attachments.LINK_MODE_IMPORTED_FILE);
|
||||
checkTestTags(savedAttachments[0]);
|
||||
|
||||
assert.equal(savedAttachments[1].getField("title"), "Link to zotero.org");
|
||||
assert.equal(savedAttachments[1].getField("url"), "http://www.zotero.org/");
|
||||
assert.equal(savedAttachments[1].getNote(), "attachment 2 note");
|
||||
assert.equal(savedAttachments[1].attachmentLinkMode, Zotero.Attachments.LINK_MODE_LINKED_URL);
|
||||
checkTestTags(savedAttachments[1]);
|
||||
}
|
||||
|
||||
assert.equal(containedAttachments[2].getField("title"), "Snapshot");
|
||||
assert.equal(containedAttachments[2].getField("url"), "http://www.example.com/");
|
||||
assert.equal(containedAttachments[2].getNote(), "attachment 3 note");
|
||||
assert.equal(containedAttachments[2].attachmentLinkMode, Zotero.Attachments.LINK_MODE_IMPORTED_URL);
|
||||
checkTestTags(containedAttachments[2]);
|
||||
});
|
||||
|
||||
it('import translators should save missing snapshots as links', function* () {
|
||||
let missingFile = getTestDataDirectory();
|
||||
missingFile.append("missing");
|
||||
assert.isFalse(missingFile.exists());
|
||||
missingFile = missingFile.path;
|
||||
let myItems = [
|
||||
{
|
||||
"itemType":"book",
|
||||
"title":"Container Item",
|
||||
"attachments":[
|
||||
{
|
||||
"itemType":"attachment",
|
||||
"path":missingFile,
|
||||
"url":"http://www.example.com/",
|
||||
"title":"Snapshot with missing file",
|
||||
"note":"attachment note",
|
||||
"tags":TEST_TAGS
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
let newItems = yield saveItemsThroughTranslator("import", myItems);
|
||||
assert.equal(newItems.length, 1);
|
||||
assert.equal(newItems[0].getField("title"), "Container Item");
|
||||
let containedAttachments = yield Zotero.Items.getAsync(newItems[0].getAttachments());
|
||||
assert.equal(containedAttachments.length, 1);
|
||||
|
||||
assert.equal(containedAttachments[0].getField("title"), "Snapshot with missing file");
|
||||
assert.equal(containedAttachments[0].getField("url"), "http://www.example.com/");
|
||||
assert.equal(containedAttachments[0].getNote(), "attachment note");
|
||||
assert.equal(containedAttachments[0].attachmentLinkMode, Zotero.Attachments.LINK_MODE_LINKED_URL);
|
||||
checkTestTags(containedAttachments[0]);
|
||||
});
|
||||
|
||||
it('import translators should ignore missing file attachments', function* () {
|
||||
let missingFile = getTestDataDirectory();
|
||||
missingFile.append("missing");
|
||||
assert.isFalse(missingFile.exists());
|
||||
missingFile = missingFile.path;
|
||||
let myItems = [
|
||||
{
|
||||
"itemType":"attachment",
|
||||
"path":missingFile,
|
||||
"title":"Missing file"
|
||||
},
|
||||
{
|
||||
"itemType":"book",
|
||||
"title":"Container Item",
|
||||
"attachments":[
|
||||
{
|
||||
"itemType":"attachment",
|
||||
"path":missingFile,
|
||||
"title":"Missing file"
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
let newItems = yield saveItemsThroughTranslator("import", myItems);
|
||||
assert.equal(newItems.length, 1);
|
||||
assert.equal(newItems[0].getField("title"), "Container Item");
|
||||
assert.equal(newItems[0].getAttachments().length, 0);
|
||||
});
|
||||
|
||||
it('web translators should save attachments', function* () {
|
||||
let myItems = [
|
||||
{
|
||||
"itemType":"book",
|
||||
"title":"Container Item",
|
||||
"attachments":[
|
||||
{
|
||||
"url":"http://www.zotero.org/",
|
||||
"title":"Link to zotero.org",
|
||||
"note":"attachment note",
|
||||
"tags":TEST_TAGS,
|
||||
"snapshot":false
|
||||
},
|
||||
{
|
||||
"url":"http://127.0.0.1:23119/test/translate/test.html",
|
||||
"title":"Test Snapshot",
|
||||
"note":"attachment 2 note",
|
||||
"tags":TEST_TAGS
|
||||
},
|
||||
{
|
||||
"url":"http://127.0.0.1:23119/test/translate/test.pdf",
|
||||
"title":"Test PDF",
|
||||
"note":"attachment 3 note",
|
||||
"tags":TEST_TAGS
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
let newItems = yield saveItemsThroughTranslator("web", myItems);
|
||||
assert.equal(newItems.length, 1);
|
||||
let containedAttachments = yield itemsArrayToObject(yield Zotero.Items.getAsync(newItems[0].getAttachments()));
|
||||
|
||||
let link = containedAttachments["Link to zotero.org"];
|
||||
assert.equal(link.getField("url"), "http://www.zotero.org/");
|
||||
assert.equal(link.getNote(), "attachment note");
|
||||
assert.equal(link.attachmentLinkMode, Zotero.Attachments.LINK_MODE_LINKED_URL);
|
||||
checkTestTags(link, true);
|
||||
|
||||
let snapshot = containedAttachments["Test Snapshot"];
|
||||
assert.equal(snapshot.getField("url"), "http://127.0.0.1:23119/test/translate/test.html");
|
||||
assert.equal(snapshot.getNote(), "attachment 2 note");
|
||||
assert.equal(snapshot.attachmentLinkMode, Zotero.Attachments.LINK_MODE_IMPORTED_URL);
|
||||
assert.equal(snapshot.attachmentContentType, "text/html");
|
||||
checkTestTags(snapshot, true);
|
||||
|
||||
let pdf = containedAttachments["Test PDF"];
|
||||
assert.equal(pdf.getField("url"), "http://127.0.0.1:23119/test/translate/test.pdf");
|
||||
assert.equal(pdf.getNote(), "attachment 3 note");
|
||||
assert.equal(pdf.attachmentLinkMode, Zotero.Attachments.LINK_MODE_IMPORTED_URL);
|
||||
assert.equal(pdf.attachmentContentType, "application/pdf");
|
||||
checkTestTags(pdf, true);
|
||||
});
|
||||
|
||||
it('web translators should save attachment from document', function* () {
|
||||
let deferred = Zotero.Promise.defer();
|
||||
let browser = Zotero.HTTP.processDocuments("http://127.0.0.1:23119/test/translate/test.html",
|
||||
function (doc) { deferred.resolve(doc) }, undefined,
|
||||
undefined, true);
|
||||
let doc = yield deferred.promise;
|
||||
|
||||
let translate = new Zotero.Translate.Web();
|
||||
translate.setDocument(doc);
|
||||
translate.setTranslator(buildDummyTranslator(4,
|
||||
'function detectWeb() {}\n'+
|
||||
'function doWeb(doc) {\n'+
|
||||
' var item = new Zotero.Item("book");\n'+
|
||||
' item.title = "Container Item";\n'+
|
||||
' item.attachments = [{\n'+
|
||||
' "document":doc,\n'+
|
||||
' "title":"Snapshot from Document",\n'+
|
||||
' "note":"attachment note",\n'+
|
||||
' "tags":'+JSON.stringify(TEST_TAGS)+'\n'+
|
||||
' }];\n'+
|
||||
' item.complete();\n'+
|
||||
'}'));
|
||||
let newItems = yield translate.translate();
|
||||
assert.equal(newItems.length, 1);
|
||||
let containedAttachments = yield Zotero.Items.getAsync(newItems[0].getAttachments());
|
||||
assert.equal(containedAttachments.length, 1);
|
||||
|
||||
let snapshot = containedAttachments[0];
|
||||
assert.equal(snapshot.getField("url"), "http://127.0.0.1:23119/test/translate/test.html");
|
||||
assert.equal(snapshot.getNote(), "attachment note");
|
||||
assert.equal(snapshot.attachmentLinkMode, Zotero.Attachments.LINK_MODE_IMPORTED_URL);
|
||||
assert.equal(snapshot.attachmentContentType, "text/html");
|
||||
checkTestTags(snapshot, true);
|
||||
|
||||
Zotero.Browser.deleteHiddenBrowser(browser);
|
||||
});
|
||||
|
||||
it('web translators should ignore attachments that return error codes', function* () {
|
||||
this.timeout(60000);
|
||||
let myItems = [
|
||||
{
|
||||
"itemType":"book",
|
||||
"title":"Container Item",
|
||||
"attachments":[
|
||||
{
|
||||
"url":"http://127.0.0.1:23119/test/translate/does_not_exist.html",
|
||||
"title":"Non-Existent HTML"
|
||||
},
|
||||
{
|
||||
"url":"http://127.0.0.1:23119/test/translate/does_not_exist.pdf",
|
||||
"title":"Non-Existent PDF"
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
let newItems = yield saveItemsThroughTranslator("web", myItems);
|
||||
assert.equal(newItems.length, 1);
|
||||
let containedAttachments = yield Zotero.Items.getAsync(newItems[0].getAttachments());
|
||||
assert.equal(containedAttachments.length, 0);
|
||||
});
|
||||
|
||||
it('web translators should save PDFs only if the content type matches', function* () {
|
||||
this.timeout(60000);
|
||||
let myItems = [
|
||||
{
|
||||
"itemType":"book",
|
||||
"title":"Container Item",
|
||||
"attachments":[
|
||||
{
|
||||
"url":"http://127.0.0.1:23119/test/translate/test.html",
|
||||
"mimeType":"application/pdf",
|
||||
"title":"Test PDF with wrong mime type"
|
||||
},
|
||||
{
|
||||
"url":"http://127.0.0.1:23119/test/translate/test.pdf",
|
||||
"mimeType":"application/pdf",
|
||||
"title":"Test PDF",
|
||||
"note":"attachment note",
|
||||
"tags":TEST_TAGS
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
let newItems = yield saveItemsThroughTranslator("web", myItems);
|
||||
assert.equal(newItems.length, 1);
|
||||
let containedAttachments = yield Zotero.Items.getAsync(newItems[0].getAttachments());
|
||||
assert.equal(containedAttachments.length, 1);
|
||||
|
||||
let pdf = containedAttachments[0];
|
||||
assert.equal(pdf.getField("title"), "Test PDF");
|
||||
assert.equal(pdf.getField("url"), "http://127.0.0.1:23119/test/translate/test.pdf");
|
||||
assert.equal(pdf.getNote(), "attachment note");
|
||||
assert.equal(pdf.attachmentLinkMode, Zotero.Attachments.LINK_MODE_IMPORTED_URL);
|
||||
checkTestTags(pdf, true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Zotero.Translate.ItemGetter", function() {
|
||||
describe("nextItem", function() {
|
||||
it('should return false for an empty database', Zotero.Promise.coroutine(function* () {
|
||||
|
@ -380,9 +926,8 @@ describe("Zotero.Translate.ItemGetter", function() {
|
|||
|
||||
it('should return stored/linked file and URI attachments in expected format', Zotero.Promise.coroutine(function* () {
|
||||
this.timeout(60000);
|
||||
let file = getTestDataDirectory();
|
||||
let file = getTestPDF();
|
||||
let item, relatedItem;
|
||||
file.append("empty.pdf");
|
||||
|
||||
yield Zotero.DB.executeTransaction(function* () {
|
||||
item = new Zotero.Item('journalArticle');
|
||||
|
@ -587,4 +1132,5 @@ describe("Zotero.Translate.ItemGetter", function() {
|
|||
}
|
||||
}));
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
Loading…
Reference in a new issue