Merge pull request #659 from aurimasv/csl-json-export
Another regression from f0bd1e77ff
This commit is contained in:
commit
4b995dd467
22 changed files with 10516 additions and 124 deletions
|
@ -524,6 +524,9 @@ Zotero.Cite.System.prototype = {
|
||||||
|
|
||||||
var cslItem = Zotero.Utilities.itemToCSLJSON(zoteroItem);
|
var cslItem = Zotero.Utilities.itemToCSLJSON(zoteroItem);
|
||||||
|
|
||||||
|
// TEMP: citeproc-js currently expects the id property to be the item DB id
|
||||||
|
cslItem.id = zoteroItem.id;
|
||||||
|
|
||||||
if (!Zotero.Prefs.get("export.citePaperJournalArticleURL")) {
|
if (!Zotero.Prefs.get("export.citePaperJournalArticleURL")) {
|
||||||
var itemType = Zotero.ItemTypes.getName(zoteroItem.itemTypeID);
|
var itemType = Zotero.ItemTypes.getName(zoteroItem.itemTypeID);
|
||||||
// don't return URL or accessed information for journal articles if a
|
// don't return URL or accessed information for journal articles if a
|
||||||
|
|
|
@ -4918,6 +4918,196 @@ Zotero.Item.prototype.serialize = function(mode) {
|
||||||
return arr;
|
return arr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serializes Zotero Item into Zotero web server API JSON format
|
||||||
|
*
|
||||||
|
* @param {Object} options
|
||||||
|
* mode {String}: [new|full|patch] "new" is default. "full" mode includes all
|
||||||
|
* fields even if empty. "patch" returns only fields that are different from
|
||||||
|
* those in patchBase
|
||||||
|
* patchBase {Object}: Item in API JSON format to be compared to in
|
||||||
|
* "patch" mode. Required if "patch" mode is specified
|
||||||
|
*/
|
||||||
|
Zotero.Item.prototype.toJSON = function(options) {
|
||||||
|
if (this.id || this.key) {
|
||||||
|
if (!this._primaryDataLoaded) {
|
||||||
|
this.loadPrimaryData(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.id) {
|
||||||
|
if (!this._itemDataLoaded) this._loadItemData();
|
||||||
|
if (this.isRegularItem() && !this._creatorsLoaded) this._loadCreators();
|
||||||
|
if (!this._relatedItemsLoaded) this._loadRelatedItems();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.hasChanged()) {
|
||||||
|
throw new Error("Cannot generate JSON from changed item");
|
||||||
|
}
|
||||||
|
|
||||||
|
options = options || {};
|
||||||
|
let mode = options.mode || 'new';
|
||||||
|
let patchBase = options.patchBase;
|
||||||
|
|
||||||
|
if (mode == 'patch') {
|
||||||
|
if (!patchBase) {
|
||||||
|
throw new Error('Cannot use "patch" mode if patchBase not provided');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (patchBase) {
|
||||||
|
Zotero.debug('Zotero.Item.toJSON: ignoring provided patchBase in "' + mode + '" mode', 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
let obj = {
|
||||||
|
key: this.key || false,
|
||||||
|
version: 1,
|
||||||
|
itemType: Zotero.ItemTypes.getName(this.itemTypeID),
|
||||||
|
tags: [],
|
||||||
|
collections: [],
|
||||||
|
relations: {}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Type-specific fields
|
||||||
|
for (let i in this._itemData) {
|
||||||
|
let val = '' + this.getField(i);
|
||||||
|
if (val !== '' || mode == 'full') {
|
||||||
|
let name = Zotero.ItemFields.getName(i);
|
||||||
|
if (name == 'version') {
|
||||||
|
// Changed in API v3 to avoid clash with 'version' above
|
||||||
|
// Remove this after https://github.com/zotero/zotero/issues/670
|
||||||
|
name = 'versionNumber';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name == 'accessDate') {
|
||||||
|
val = Zotero.Date.dateToISO(Zotero.Date.sqlToDate(val));
|
||||||
|
}
|
||||||
|
|
||||||
|
obj[name] = val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.isRegularItem()) {
|
||||||
|
// Creators
|
||||||
|
obj.creators = [];
|
||||||
|
let creators = this.getCreators();
|
||||||
|
for (let i=0; i<creators.length; i++) {
|
||||||
|
let creator = creators[i].ref;
|
||||||
|
let creatorObj = {
|
||||||
|
creatorType: Zotero.CreatorTypes.getName(creators[i].creatorTypeID)
|
||||||
|
};
|
||||||
|
|
||||||
|
if (creator.fieldMode == 1) {
|
||||||
|
creatorObj.name = creator.lastName;
|
||||||
|
} else {
|
||||||
|
creatorObj.lastName = creator.lastName;
|
||||||
|
creatorObj.firstName = creator.firstName;
|
||||||
|
}
|
||||||
|
|
||||||
|
obj.creators.push(creatorObj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Notes or Attachments
|
||||||
|
let parent = this.getSourceKey();
|
||||||
|
if (parent || mode == 'full') {
|
||||||
|
obj.parentItem = parent ? parent : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notes and embedded attachment notes
|
||||||
|
let note = this.getNote();
|
||||||
|
if (note !== "" || mode == 'full') {
|
||||||
|
obj.note = note;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attachment fields
|
||||||
|
if (this.isAttachment()) {
|
||||||
|
obj.linkMode = ['imported_file','imported_url','linked_file','linked_url'][this.attachmentLinkMode];
|
||||||
|
obj.contentType = this.attachmentMIMEType;
|
||||||
|
obj.charset = this.attachmentCharset;
|
||||||
|
obj.path = this.attachmentPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tags
|
||||||
|
let tags = this.getTags();
|
||||||
|
for (let i=0; i<tags.length; i++) {
|
||||||
|
let tag = {
|
||||||
|
tag: tags[i].name
|
||||||
|
};
|
||||||
|
if (tags[i].type) tag.type = tags[i].type
|
||||||
|
|
||||||
|
obj.tags.push(tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collections
|
||||||
|
if (this.id) {
|
||||||
|
let collections = this.getCollections();
|
||||||
|
for (let i=0; i<collections.length; i++) {
|
||||||
|
let collection = Zotero.Collections.get(collections[i]);
|
||||||
|
obj.collections.push(collection.key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Relations
|
||||||
|
if (this.key) {
|
||||||
|
// Relations other than through the "Related" tab
|
||||||
|
let itemURI = Zotero.URI.getItemURI(this),
|
||||||
|
rels = Zotero.Relations.getByURIs(itemURI);
|
||||||
|
for (let i=0; i<rels.length; i++) {
|
||||||
|
let rel = rels[i].load();
|
||||||
|
obj.relations[rel.predicate] = rel.object;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Related items (in both directions)
|
||||||
|
let relatedItems = this._getRelatedItemsBidirectional();
|
||||||
|
let pred = 'dc:relation';
|
||||||
|
for (let i=0; i<relatedItems.length; i++) {
|
||||||
|
let item = Zotero.Items.get(relatedItems[i]);
|
||||||
|
let uri = Zotero.URI.getItemURI(item);
|
||||||
|
if (obj.relations[pred]) {
|
||||||
|
if (typeof obj.relations[pred] == 'string') {
|
||||||
|
obj.relations[pred] = [obj.relations[pred]];
|
||||||
|
}
|
||||||
|
obj.relations[pred].push(uri)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
obj.relations[pred] = uri;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deleted
|
||||||
|
let deleted = this.deleted;
|
||||||
|
if (deleted || mode == 'full') {
|
||||||
|
obj.deleted = deleted;
|
||||||
|
}
|
||||||
|
|
||||||
|
obj.dateAdded = Zotero.Date.sqlToISO8601(this.dateAdded);
|
||||||
|
obj.dateModified = Zotero.Date.sqlToISO8601(this.dateModified);
|
||||||
|
|
||||||
|
if (mode == 'patch') {
|
||||||
|
// For "patch" mode, remove fields that have the same values
|
||||||
|
for (let i in patchBase) {
|
||||||
|
switch (i) {
|
||||||
|
case 'itemKey':
|
||||||
|
case 'itemVersion':
|
||||||
|
case 'dateModified':
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i in obj) {
|
||||||
|
if (obj[i] === patchBase[i]) {
|
||||||
|
delete obj[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
obj[i] = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return obj;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -5098,7 +5288,7 @@ Zotero.Item.prototype._getRelatedItemsBidirectional = function () {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (!related) {
|
else if (!related.length) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
return related;
|
return related;
|
||||||
|
|
|
@ -130,7 +130,8 @@ Zotero.ItemFields = new function() {
|
||||||
|
|
||||||
|
|
||||||
function isValidForType(fieldID, itemTypeID) {
|
function isValidForType(fieldID, itemTypeID) {
|
||||||
_fieldCheck(fieldID, 'isValidForType');
|
fieldID = getID(fieldID);
|
||||||
|
if (!fieldID) return false;
|
||||||
|
|
||||||
if (!_fields[fieldID]['itemTypes']) {
|
if (!_fields[fieldID]['itemTypes']) {
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -544,6 +544,29 @@ Zotero.Date = new function(){
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
this.sqlToISO8601 = function (sqlDate) {
|
||||||
|
var date = sqlDate.substr(0, 10);
|
||||||
|
var matches = date.match(/^([0-9]{4})\-([0-9]{2})\-([0-9]{2})/);
|
||||||
|
if (!matches) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
date = matches[1];
|
||||||
|
// Drop parts for reduced precision
|
||||||
|
if (matches[2] !== "00") {
|
||||||
|
date += "-" + matches[2];
|
||||||
|
if (matches[3] !== "00") {
|
||||||
|
date += "-" + matches[3];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var time = sqlDate.substr(11);
|
||||||
|
// TODO: validate times
|
||||||
|
if (time) {
|
||||||
|
date += "T" + time + "Z";
|
||||||
|
}
|
||||||
|
return date;
|
||||||
|
}
|
||||||
|
|
||||||
function strToMultipart(str){
|
function strToMultipart(str){
|
||||||
if (!str){
|
if (!str){
|
||||||
return '';
|
return '';
|
||||||
|
|
|
@ -2186,6 +2186,10 @@ Zotero.Translate.Export.prototype._prepareTranslation = function() {
|
||||||
|
|
||||||
// initialize ItemGetter
|
// initialize ItemGetter
|
||||||
this._itemGetter = new Zotero.Translate.ItemGetter();
|
this._itemGetter = new Zotero.Translate.ItemGetter();
|
||||||
|
|
||||||
|
// Toggle legacy mode for translators pre-4.0.27
|
||||||
|
this._itemGetter.legacy = Services.vc.compare('4.0.27', this._translatorInfo.minVersion) > 0;
|
||||||
|
|
||||||
var configOptions = this._translatorInfo.configOptions || {},
|
var configOptions = this._translatorInfo.configOptions || {},
|
||||||
getCollections = configOptions.getCollections || false;
|
getCollections = configOptions.getCollections || false;
|
||||||
switch (this._export.type) {
|
switch (this._export.type) {
|
||||||
|
|
|
@ -745,9 +745,10 @@ Zotero.Translate.ItemSaver.prototype = {
|
||||||
}
|
}
|
||||||
|
|
||||||
Zotero.Translate.ItemGetter = function() {
|
Zotero.Translate.ItemGetter = function() {
|
||||||
this._itemsLeft = null;
|
this._itemsLeft = [];
|
||||||
this._collectionsLeft = null;
|
this._collectionsLeft = null;
|
||||||
this._exportFileDirectory = null;
|
this._exportFileDirectory = null;
|
||||||
|
this.legacy = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
Zotero.Translate.ItemGetter.prototype = {
|
Zotero.Translate.ItemGetter.prototype = {
|
||||||
|
@ -828,13 +829,8 @@ Zotero.Translate.ItemGetter.prototype = {
|
||||||
* Converts an attachment to array format and copies it to the export folder if desired
|
* Converts an attachment to array format and copies it to the export folder if desired
|
||||||
*/
|
*/
|
||||||
"_attachmentToArray":function(attachment) {
|
"_attachmentToArray":function(attachment) {
|
||||||
var attachmentArray = this._itemToArray(attachment);
|
var attachmentArray = Zotero.Utilities.Internal.itemToExportFormat(attachment, this.legacy);
|
||||||
var linkMode = attachment.attachmentLinkMode;
|
var linkMode = attachment.attachmentLinkMode;
|
||||||
|
|
||||||
// Get mime type
|
|
||||||
attachmentArray.mimeType = attachmentArray.uniqueFields.mimeType = attachment.attachmentMIMEType;
|
|
||||||
// Get charset
|
|
||||||
attachmentArray.charset = attachmentArray.uniqueFields.charset = attachment.attachmentCharset;
|
|
||||||
if(linkMode != Zotero.Attachments.LINK_MODE_LINKED_URL) {
|
if(linkMode != Zotero.Attachments.LINK_MODE_LINKED_URL) {
|
||||||
var attachFile = attachment.getFile();
|
var attachFile = attachment.getFile();
|
||||||
attachmentArray.localPath = attachFile.path;
|
attachmentArray.localPath = attachFile.path;
|
||||||
|
@ -845,7 +841,7 @@ Zotero.Translate.ItemGetter.prototype = {
|
||||||
// Add path and filename if not an internet link
|
// Add path and filename if not an internet link
|
||||||
var attachFile = attachment.getFile();
|
var attachFile = attachment.getFile();
|
||||||
if(attachFile) {
|
if(attachFile) {
|
||||||
attachmentArray.defaultPath = "files/" + attachmentArray.itemID + "/" + attachFile.leafName;
|
attachmentArray.defaultPath = "files/" + attachment.id + "/" + attachFile.leafName;
|
||||||
attachmentArray.filename = attachFile.leafName;
|
attachmentArray.filename = attachFile.leafName;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -959,39 +955,8 @@ Zotero.Translate.ItemGetter.prototype = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
attachmentArray.itemType = "attachment";
|
|
||||||
|
|
||||||
return attachmentArray;
|
return attachmentArray;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts an item to array format
|
|
||||||
*/
|
|
||||||
"_itemToArray":function(returnItem) {
|
|
||||||
// TODO use Zotero.Item#serialize()
|
|
||||||
var returnItemArray = returnItem.toArray();
|
|
||||||
|
|
||||||
// Remove SQL date from multipart dates
|
|
||||||
if (returnItemArray.date) {
|
|
||||||
returnItemArray.date = Zotero.Date.multipartToStr(returnItemArray.date);
|
|
||||||
}
|
|
||||||
|
|
||||||
var returnItemArray = Zotero.Utilities.itemToExportFormat(returnItemArray);
|
|
||||||
|
|
||||||
// TODO: Change tag.tag references in translators to tag.name
|
|
||||||
// once translators are 1.5-only
|
|
||||||
// TODO: Preserve tag type?
|
|
||||||
if (returnItemArray.tags) {
|
|
||||||
for (var i in returnItemArray.tags) {
|
|
||||||
returnItemArray.tags[i].tag = returnItemArray.tags[i].fields.name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// add URI
|
|
||||||
returnItemArray.uri = Zotero.URI.getItemURI(returnItem);
|
|
||||||
|
|
||||||
return returnItemArray;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the next available item
|
* Retrieves the next available item
|
||||||
|
@ -1004,10 +969,10 @@ Zotero.Translate.ItemGetter.prototype = {
|
||||||
var returnItemArray = this._attachmentToArray(returnItem);
|
var returnItemArray = this._attachmentToArray(returnItem);
|
||||||
if(returnItemArray) return returnItemArray;
|
if(returnItemArray) return returnItemArray;
|
||||||
} else {
|
} else {
|
||||||
var returnItemArray = this._itemToArray(returnItem);
|
var returnItemArray = Zotero.Utilities.Internal.itemToExportFormat(returnItem, this.legacy);
|
||||||
|
|
||||||
// get attachments, although only urls will be passed if exportFileData is off
|
// get attachments, although only urls will be passed if exportFileData is off
|
||||||
returnItemArray.attachments = new Array();
|
returnItemArray.attachments = [];
|
||||||
var attachments = returnItem.getAttachments();
|
var attachments = returnItem.getAttachments();
|
||||||
for each(var attachmentID in attachments) {
|
for each(var attachmentID in attachments) {
|
||||||
var attachment = Zotero.Items.get(attachmentID);
|
var attachment = Zotero.Items.get(attachmentID);
|
||||||
|
|
|
@ -61,7 +61,7 @@ const CSL_TEXT_MAPPINGS = {
|
||||||
"number-of-volumes":["numberOfVolumes"],
|
"number-of-volumes":["numberOfVolumes"],
|
||||||
"number-of-pages":["numPages"],
|
"number-of-pages":["numPages"],
|
||||||
"edition":["edition"],
|
"edition":["edition"],
|
||||||
"version":["version"],
|
"version":["versionNumber"],
|
||||||
"section":["section", "committee"],
|
"section":["section", "committee"],
|
||||||
"genre":["type", "programmingLanguage"],
|
"genre":["type", "programmingLanguage"],
|
||||||
"source":["libraryCatalog"],
|
"source":["libraryCatalog"],
|
||||||
|
@ -133,7 +133,10 @@ const CSL_TYPE_MAPPINGS = {
|
||||||
'tvBroadcast':"broadcast",
|
'tvBroadcast':"broadcast",
|
||||||
'radioBroadcast':"broadcast",
|
'radioBroadcast':"broadcast",
|
||||||
'podcast':"song", // ??
|
'podcast':"song", // ??
|
||||||
'computerProgram':"book" // ??
|
'computerProgram':"book", // ??
|
||||||
|
'document':"article",
|
||||||
|
'note':"article",
|
||||||
|
'attachment':"article"
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1345,49 +1348,6 @@ Zotero.Utilities = {
|
||||||
return dumpedText;
|
return dumpedText;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds all fields to an item in toArray() format and adds a unique (base) fields to
|
|
||||||
* uniqueFields array
|
|
||||||
*/
|
|
||||||
"itemToExportFormat":function(item) {
|
|
||||||
const CREATE_ARRAYS = ['creators', 'notes', 'tags', 'seeAlso', 'attachments'];
|
|
||||||
for(var i=0; i<CREATE_ARRAYS.length; i++) {
|
|
||||||
var createArray = CREATE_ARRAYS[i];
|
|
||||||
if(!item[createArray]) item[createArray] = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
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 an array of items in
|
* Converts an item from toArray() format to an array of items in
|
||||||
* the content=json format used by the server
|
* the content=json format used by the server
|
||||||
|
@ -1527,14 +1487,16 @@ Zotero.Utilities = {
|
||||||
*/
|
*/
|
||||||
"itemToCSLJSON":function(zoteroItem) {
|
"itemToCSLJSON":function(zoteroItem) {
|
||||||
if (zoteroItem instanceof Zotero.Item) {
|
if (zoteroItem instanceof Zotero.Item) {
|
||||||
zoteroItem = zoteroItem.toArray();
|
zoteroItem = Zotero.Utilities.Internal.itemToExportFormat(zoteroItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
var cslType = CSL_TYPE_MAPPINGS[zoteroItem.itemType] || "article";
|
var cslType = CSL_TYPE_MAPPINGS[zoteroItem.itemType];
|
||||||
|
if (!cslType) throw new Error('Unexpected Zotero Item type "' + zoteroItem.itemType + '"');
|
||||||
|
|
||||||
var itemTypeID = Zotero.ItemTypes.getID(zoteroItem.itemType);
|
var itemTypeID = Zotero.ItemTypes.getID(zoteroItem.itemType);
|
||||||
|
|
||||||
var cslItem = {
|
var cslItem = {
|
||||||
'id':zoteroItem.itemID,
|
'id':zoteroItem.uri,
|
||||||
'type':cslType
|
'type':cslType
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1548,11 +1510,13 @@ Zotero.Utilities = {
|
||||||
if(field in zoteroItem) {
|
if(field in zoteroItem) {
|
||||||
value = zoteroItem[field];
|
value = zoteroItem[field];
|
||||||
} else {
|
} else {
|
||||||
|
if (field == 'versionNumber') field = 'version'; // Until https://github.com/zotero/zotero/issues/670
|
||||||
var fieldID = Zotero.ItemFields.getID(field),
|
var fieldID = Zotero.ItemFields.getID(field),
|
||||||
baseMapping;
|
typeFieldID;
|
||||||
if(Zotero.ItemFields.isValidForType(fieldID, itemTypeID)
|
if(fieldID
|
||||||
&& (baseMapping = Zotero.ItemFields.getBaseIDFromTypeAndField(itemTypeID, fieldID))) {
|
&& (typeFieldID = Zotero.ItemFields.getFieldIDFromTypeAndBase(itemTypeID, fieldID))
|
||||||
value = zoteroItem[Zotero.ItemTypes.getName(baseMapping)];
|
) {
|
||||||
|
value = zoteroItem[Zotero.ItemFields.getName(typeFieldID)];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1578,7 +1542,7 @@ Zotero.Utilities = {
|
||||||
// separate name variables
|
// separate name variables
|
||||||
var author = Zotero.CreatorTypes.getName(Zotero.CreatorTypes.getPrimaryIDForType(itemTypeID));
|
var author = Zotero.CreatorTypes.getName(Zotero.CreatorTypes.getPrimaryIDForType(itemTypeID));
|
||||||
var creators = zoteroItem.creators;
|
var creators = zoteroItem.creators;
|
||||||
for(var i=0; i<creators.length; i++) {
|
for(var i=0; creators && i<creators.length; i++) {
|
||||||
var creator = creators[i];
|
var creator = creators[i];
|
||||||
var creatorType = creator.creatorType;
|
var creatorType = creator.creatorType;
|
||||||
if(creatorType == author) {
|
if(creatorType == author) {
|
||||||
|
@ -1600,6 +1564,13 @@ Zotero.Utilities = {
|
||||||
// get date variables
|
// get date variables
|
||||||
for(var variable in CSL_DATE_MAPPINGS) {
|
for(var variable in CSL_DATE_MAPPINGS) {
|
||||||
var date = zoteroItem[CSL_DATE_MAPPINGS[variable]];
|
var date = zoteroItem[CSL_DATE_MAPPINGS[variable]];
|
||||||
|
if (!date) {
|
||||||
|
var typeSpecificFieldID = Zotero.ItemFields.getFieldIDFromTypeAndBase(itemTypeID, CSL_DATE_MAPPINGS[variable]);
|
||||||
|
if (typeSpecificFieldID) {
|
||||||
|
date = zoteroItem[Zotero.ItemFields.getName(typeSpecificFieldID)];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if(date) {
|
if(date) {
|
||||||
var dateObj = Zotero.Date.strToDate(date);
|
var dateObj = Zotero.Date.strToDate(date);
|
||||||
// otherwise, use date-parts
|
// otherwise, use date-parts
|
||||||
|
@ -1625,7 +1596,12 @@ Zotero.Utilities = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Special mapping for note title
|
||||||
|
if (zoteroItem.itemType == 'note' && zoteroItem.note) {
|
||||||
|
cslItem.title = Zotero.Notes.noteToTitle(zoteroItem.note);
|
||||||
|
}
|
||||||
|
|
||||||
// extract PMID
|
// extract PMID
|
||||||
var extra = zoteroItem.extra;
|
var extra = zoteroItem.extra;
|
||||||
if(typeof extra === "string") {
|
if(typeof extra === "string") {
|
||||||
|
|
|
@ -220,7 +220,6 @@ Zotero.Utilities.Internal = {
|
||||||
return s;
|
return s;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display a prompt from an error with custom buttons and a callback
|
* Display a prompt from an error with custom buttons and a callback
|
||||||
*/
|
*/
|
||||||
|
@ -370,6 +369,139 @@ Zotero.Utilities.Internal = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts Zotero.Item to a format expected by translators
|
||||||
|
* This is mostly the Zotero web API item JSON format, but with an attachments
|
||||||
|
* and notes arrays and optional compatibility mappings for older translators.
|
||||||
|
*
|
||||||
|
* @param {Zotero.Item} zoteroItem
|
||||||
|
* @param {Boolean} legacy Add mappings for legacy (pre-4.0.27) translators
|
||||||
|
* @return {Object}
|
||||||
|
*/
|
||||||
|
"itemToExportFormat": new function() {
|
||||||
|
return function(zoteroItem, legacy) {
|
||||||
|
var item = zoteroItem.toJSON();
|
||||||
|
item.uri = Zotero.URI.getItemURI(zoteroItem);
|
||||||
|
delete item.key;
|
||||||
|
|
||||||
|
if (!zoteroItem.isAttachment() && !zoteroItem.isNote()) {
|
||||||
|
// Include attachments
|
||||||
|
item.attachments = [];
|
||||||
|
let attachments = zoteroItem.getAttachments();
|
||||||
|
for (let i=0; i<attachments.length; i++) {
|
||||||
|
let zoteroAttachment = Zotero.Items.get(attachments[i]),
|
||||||
|
attachment = zoteroAttachment.toJSON();
|
||||||
|
if (legacy) addCompatibilityMappings(attachment, zoteroAttachment);
|
||||||
|
|
||||||
|
item.attachments.push(attachment);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Include notes
|
||||||
|
item.notes = [];
|
||||||
|
let notes = zoteroItem.getNotes();
|
||||||
|
for (let i=0; i<notes.length; i++) {
|
||||||
|
let zoteroNote = Zotero.Items.get(notes[i]),
|
||||||
|
note = zoteroNote.toJSON();
|
||||||
|
if (legacy) addCompatibilityMappings(note, zoteroNote);
|
||||||
|
|
||||||
|
item.notes.push(note);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (legacy) addCompatibilityMappings(item, zoteroItem);
|
||||||
|
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
function addCompatibilityMappings(item, zoteroItem) {
|
||||||
|
item.uniqueFields = {};
|
||||||
|
|
||||||
|
// Meaningless local item ID, but some older export translators depend on it
|
||||||
|
item.itemID = zoteroItem.id;
|
||||||
|
item.key = zoteroItem.key; // CSV translator exports this
|
||||||
|
|
||||||
|
// "version" is expected to be a field for "computerProgram", which is now
|
||||||
|
// called "versionNumber"
|
||||||
|
delete item.version;
|
||||||
|
if (item.versionNumber) {
|
||||||
|
item.version = item.uniqueFields.version = item.versionNumber;
|
||||||
|
delete item.versionNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
// SQL instead of ISO-8601
|
||||||
|
item.dateAdded = zoteroItem.dateAdded;
|
||||||
|
item.dateModified = zoteroItem.dateModified;
|
||||||
|
if (item.accessDate) {
|
||||||
|
item.accessDate = zoteroItem.getField('accessDate');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map base fields
|
||||||
|
for (let field in item) {
|
||||||
|
let id = Zotero.ItemFields.getID(field);
|
||||||
|
if (!id || !Zotero.ItemFields.isValidForType(id, zoteroItem.itemTypeID)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let baseField = Zotero.ItemFields.getName(
|
||||||
|
Zotero.ItemFields.getBaseIDFromTypeAndField(item.itemType, field)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!baseField || baseField == field) {
|
||||||
|
item.uniqueFields[field] = item[field];
|
||||||
|
} else {
|
||||||
|
item[baseField] = item[field];
|
||||||
|
item.uniqueFields[baseField] = item[field];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add various fields for compatibility with translators pre-4.0.27
|
||||||
|
item.itemID = zoteroItem.id;
|
||||||
|
item.libraryID = zoteroItem.libraryID;
|
||||||
|
|
||||||
|
// Creators
|
||||||
|
if (item.creators) {
|
||||||
|
for (let i=0; i<item.creators.length; i++) {
|
||||||
|
let creator = item.creators[i];
|
||||||
|
|
||||||
|
if (creator.name) {
|
||||||
|
creator.fieldMode = 1;
|
||||||
|
creator.lastName = creator.name;
|
||||||
|
delete creator.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Old format used to supply creatorID (the database ID), but no
|
||||||
|
// translator ever used it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!zoteroItem.isRegularItem()) {
|
||||||
|
item.sourceItemKey = item.parentItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tags
|
||||||
|
for (let i=0; i<item.tags.length; i++) {
|
||||||
|
if (!item.tags[i].type) {
|
||||||
|
item.tags[i].type = 0;
|
||||||
|
}
|
||||||
|
// No translator ever used "primary", "fields", or "linkedItems" objects
|
||||||
|
}
|
||||||
|
|
||||||
|
// "related" was never used (array of itemIDs)
|
||||||
|
|
||||||
|
// seeAlso was always present, but it was always an empty array.
|
||||||
|
// Zotero RDF translator pretended to use it
|
||||||
|
item.seeAlso = [];
|
||||||
|
|
||||||
|
// Fix linkMode
|
||||||
|
if (zoteroItem.isAttachment()) {
|
||||||
|
item.linkMode = zoteroItem.attachmentLinkMode;
|
||||||
|
item.mimeType = item.contentType;
|
||||||
|
}
|
||||||
|
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hyphenate an ISBN based on the registrant table available from
|
* Hyphenate an ISBN based on the registrant table available from
|
||||||
* https://www.isbn-international.org/range_file_generation
|
* https://www.isbn-international.org/range_file_generation
|
||||||
|
|
|
@ -33,6 +33,9 @@ ZoteroUnit.prototype = {
|
||||||
handle:function(cmdLine) {
|
handle:function(cmdLine) {
|
||||||
this.tests = cmdLine.handleFlagWithParam("test", false);
|
this.tests = cmdLine.handleFlagWithParam("test", false);
|
||||||
this.noquit = cmdLine.handleFlag("noquit", false);
|
this.noquit = cmdLine.handleFlag("noquit", false);
|
||||||
|
this.makeTestData = cmdLine.handleFlag("makeTestData", false);
|
||||||
|
this.noquit = !this.makeTestData && this.noquit;
|
||||||
|
this.runTests = !this.makeTestData;
|
||||||
},
|
},
|
||||||
|
|
||||||
dump:function(x) {
|
dump:function(x) {
|
||||||
|
|
|
@ -1,17 +1,16 @@
|
||||||
Components.utils.import("resource://gre/modules/FileUtils.jsm");
|
|
||||||
Components.utils.import("resource://gre/modules/osfile.jsm");
|
Components.utils.import("resource://gre/modules/osfile.jsm");
|
||||||
Components.utils.import("resource://zotero/q.js");
|
|
||||||
var EventUtils = Components.utils.import("resource://zotero-unit/EventUtils.jsm");
|
var EventUtils = Components.utils.import("resource://zotero-unit/EventUtils.jsm");
|
||||||
|
|
||||||
var ZoteroUnit = Components.classes["@mozilla.org/commandlinehandler/general-startup;1?type=zotero-unit"].
|
var ZoteroUnit = Components.classes["@mozilla.org/commandlinehandler/general-startup;1?type=zotero-unit"].
|
||||||
getService(Components.interfaces.nsISupports).
|
getService(Components.interfaces.nsISupports).
|
||||||
wrappedJSObject;
|
wrappedJSObject;
|
||||||
|
|
||||||
var dump = ZoteroUnit.dump;
|
var dump = ZoteroUnit.dump;
|
||||||
|
|
||||||
function quit(failed) {
|
function quit(failed) {
|
||||||
// Quit with exit status
|
// Quit with exit status
|
||||||
if(!failed) {
|
if(!failed) {
|
||||||
OS.File.writeAtomic(FileUtils.getFile("ProfD", ["success"]).path, Uint8Array(0));
|
OS.File.writeAtomic(OS.Path.join(OS.Constants.Path.profileDir, "success"), new Uint8Array(0));
|
||||||
}
|
}
|
||||||
if(!ZoteroUnit.noquit) {
|
if(!ZoteroUnit.noquit) {
|
||||||
Components.classes['@mozilla.org/toolkit/app-startup;1'].
|
Components.classes['@mozilla.org/toolkit/app-startup;1'].
|
||||||
|
@ -20,6 +19,72 @@ function quit(failed) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ZoteroUnit.makeTestData) {
|
||||||
|
let dataPath = getTestDataDirectory().path;
|
||||||
|
|
||||||
|
Zotero.Prefs.set("export.citePaperJournalArticleURL", true);
|
||||||
|
|
||||||
|
let dataFiles = [
|
||||||
|
{
|
||||||
|
name: 'allTypesAndFields',
|
||||||
|
func: generateAllTypesAndFieldsData
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'itemJSON',
|
||||||
|
func: generateItemJSONData,
|
||||||
|
args: [null]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'citeProcJSExport',
|
||||||
|
func: generateCiteProcJSExportData
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'translatorExportLegacy',
|
||||||
|
func: generateTranslatorExportData,
|
||||||
|
args: [true]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'translatorExport',
|
||||||
|
func: generateTranslatorExportData,
|
||||||
|
args: [false]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
let p = Q.resolve();
|
||||||
|
for (let i=0; i<dataFiles.length; i++) {
|
||||||
|
let first = !i;
|
||||||
|
let params = dataFiles[i];
|
||||||
|
|
||||||
|
p = p.then(function() {
|
||||||
|
// Make sure to not run next loop if previous fails
|
||||||
|
return Q.try(function() {
|
||||||
|
if (!first) dump('\n');
|
||||||
|
dump('Generating data for ' + params.name + '...');
|
||||||
|
|
||||||
|
let filePath = OS.Path.join(dataPath, params.name + '.js');
|
||||||
|
|
||||||
|
return Q.resolve(OS.File.exists(filePath))
|
||||||
|
.then(function(exists) {
|
||||||
|
let currentData;
|
||||||
|
if (exists) {
|
||||||
|
currentData = loadSampleData(params.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
let args = params.args || [];
|
||||||
|
args.push(currentData);
|
||||||
|
let str = stableStringify(params.func.apply(null, args));
|
||||||
|
|
||||||
|
return OS.File.writeAtomic(OS.Path.join(dataPath, params.name + '.js'), str);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.then(function() { dump("done."); })
|
||||||
|
.catch(function(e) { dump("failed!"); throw e })
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
p.catch(function(e) { dump('\n'); dump(Zotero.Utilities.varDump(e)) })
|
||||||
|
.finally(function() { quit(false) });
|
||||||
|
}
|
||||||
|
|
||||||
function Reporter(runner) {
|
function Reporter(runner) {
|
||||||
var indents = 0, passed = 0, failed = 0;
|
var indents = 0, passed = 0, failed = 0;
|
||||||
|
|
||||||
|
@ -40,7 +105,7 @@ function Reporter(runner) {
|
||||||
});
|
});
|
||||||
|
|
||||||
runner.on('pending', function(test){
|
runner.on('pending', function(test){
|
||||||
dump(indent()+"pending -"+test.title);
|
dump("\r"+indent()+"pending -"+test.title+"\n");
|
||||||
});
|
});
|
||||||
|
|
||||||
runner.on('pass', function(test){
|
runner.on('pass', function(test){
|
||||||
|
@ -71,8 +136,8 @@ var assert = chai.assert,
|
||||||
expect = chai.expect;
|
expect = chai.expect;
|
||||||
|
|
||||||
// Set up tests to run
|
// Set up tests to run
|
||||||
var run = true;
|
var run = ZoteroUnit.runTests;
|
||||||
if(ZoteroUnit.tests) {
|
if(run && ZoteroUnit.tests) {
|
||||||
var testDirectory = getTestDataDirectory().parent,
|
var testDirectory = getTestDataDirectory().parent,
|
||||||
testFiles = [];
|
testFiles = [];
|
||||||
if(ZoteroUnit.tests == "all") {
|
if(ZoteroUnit.tests == "all") {
|
||||||
|
|
|
@ -1,3 +1,10 @@
|
||||||
|
Components.utils.import("resource://zotero/q.js");
|
||||||
|
|
||||||
|
// Useful "constants"
|
||||||
|
var sqlDateTimeRe = /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/;
|
||||||
|
var isoDateTimeRe = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$/;
|
||||||
|
var zoteroObjectKeyRe = /^[23456789ABCDEFGHIJKMNPQRSTUVWXZ]{8}$/; // based on Zotero.Utilities::generateObjectKey()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Waits for a DOM event on the specified node. Returns a promise
|
* Waits for a DOM event on the specified node. Returns a promise
|
||||||
* resolved with the event.
|
* resolved with the event.
|
||||||
|
@ -144,8 +151,8 @@ function installPDFTools() {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a promise for the nsIFile corresponding to the test data
|
* Returns the nsIFile corresponding to the test data directory
|
||||||
* directory (i.e., test/tests/data)
|
* (i.e., test/tests/data)
|
||||||
*/
|
*/
|
||||||
function getTestDataDirectory() {
|
function getTestDataDirectory() {
|
||||||
Components.utils.import("resource://gre/modules/Services.jsm");
|
Components.utils.import("resource://gre/modules/Services.jsm");
|
||||||
|
@ -156,6 +163,28 @@ function getTestDataDirectory() {
|
||||||
QueryInterface(Components.interfaces.nsIFileURL).file;
|
QueryInterface(Components.interfaces.nsIFileURL).file;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an absolute path to an empty temporary directory
|
||||||
|
* (i.e., test/tests/data)
|
||||||
|
*/
|
||||||
|
var getTempDirectory = Q.async(function getTempDirectory() {
|
||||||
|
Components.utils.import("resource://gre/modules/osfile.jsm");
|
||||||
|
let path,
|
||||||
|
attempts = 3,
|
||||||
|
zoteroTmpDirPath = Zotero.getTempDirectory().path;
|
||||||
|
while (attempts--) {
|
||||||
|
path = OS.Path.join(zoteroTmpDirPath, Zotero.Utilities.randomString());
|
||||||
|
try {
|
||||||
|
yield OS.File.makeDir(path, { ignoreExisting: false });
|
||||||
|
break;
|
||||||
|
} catch (e) {
|
||||||
|
if (!attempts) throw e; // Throw on last attempt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Q.return(path);
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resets the Zotero DB and restarts Zotero. Returns a promise resolved
|
* Resets the Zotero DB and restarts Zotero. Returns a promise resolved
|
||||||
* when this finishes.
|
* when this finishes.
|
||||||
|
@ -167,4 +196,298 @@ function resetDB() {
|
||||||
}).then(function() {
|
}).then(function() {
|
||||||
return Zotero.Schema.schemaUpdatePromise;
|
return Zotero.Schema.schemaUpdatePromise;
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Equivalent to JSON.stringify, except that object properties are stringified
|
||||||
|
* in a sorted order.
|
||||||
|
*/
|
||||||
|
function stableStringify(obj, level, label) {
|
||||||
|
if (!level) level = 0;
|
||||||
|
let indent = '\t'.repeat(level);
|
||||||
|
|
||||||
|
if (label) label = JSON.stringify('' + label) + ': ';
|
||||||
|
else label = '';
|
||||||
|
|
||||||
|
if (typeof obj == 'function' || obj === undefined) return null;
|
||||||
|
|
||||||
|
if (typeof obj != 'object' || obj === null) return indent + label + JSON.stringify(obj);
|
||||||
|
|
||||||
|
if (Array.isArray(obj)) {
|
||||||
|
let str = indent + label + '[';
|
||||||
|
for (let i=0; i<obj.length; i++) {
|
||||||
|
let json = stableStringify(obj[i], level + 1);
|
||||||
|
if (json === null) json = indent + '\tnull'; // function
|
||||||
|
str += '\n' + json + (i < obj.length-1 ? ',' : '');
|
||||||
|
}
|
||||||
|
return str + (obj.length ? '\n' + indent : '') + ']';
|
||||||
|
}
|
||||||
|
|
||||||
|
let keys = Object.keys(obj).sort(),
|
||||||
|
empty = true,
|
||||||
|
str = indent + label + '{';
|
||||||
|
for (let i=0; i<keys.length; i++) {
|
||||||
|
let json = stableStringify(obj[keys[i]], level + 1, keys[i]);
|
||||||
|
if (json === null) continue; // function
|
||||||
|
|
||||||
|
empty = false;
|
||||||
|
str += '\n' + json + (i < keys.length-1 ? ',' : '');
|
||||||
|
}
|
||||||
|
|
||||||
|
return str + (!empty ? '\n' + indent : '') + '}';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads specified sample data from file
|
||||||
|
*/
|
||||||
|
function loadSampleData(dataName) {
|
||||||
|
let data = Zotero.File.getContentsFromURL('resource://zotero-unit-tests/data/' + dataName + '.js');
|
||||||
|
return JSON.parse(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates sample item data that is stored in data/sampleItemData.js
|
||||||
|
*/
|
||||||
|
function generateAllTypesAndFieldsData() {
|
||||||
|
let data = {};
|
||||||
|
let itemTypes = Zotero.ItemTypes.getTypes();
|
||||||
|
// For most fields, use the field name as the value, but this doesn't
|
||||||
|
// work well for some fields that expect values in certain formats
|
||||||
|
let specialValues = {
|
||||||
|
date: '1999-12-31',
|
||||||
|
filingDate: '2000-01-02',
|
||||||
|
accessDate: '1997-06-13 23:59:58',
|
||||||
|
number: 3,
|
||||||
|
numPages: 4,
|
||||||
|
issue: 5,
|
||||||
|
volume: 6,
|
||||||
|
numberOfVolumes: 7,
|
||||||
|
edition: 8,
|
||||||
|
seriesNumber: 9,
|
||||||
|
ISBN: '978-1-234-56789-7',
|
||||||
|
ISSN: '1234-5679',
|
||||||
|
url: 'http://www.example.com',
|
||||||
|
pages: '1-10',
|
||||||
|
DOI: '10.1234/example.doi',
|
||||||
|
runningTime: '1:22:33',
|
||||||
|
language: 'en-US'
|
||||||
|
};
|
||||||
|
|
||||||
|
// Item types that should not be included in sample data
|
||||||
|
let excludeItemTypes = ['note', 'attachment'];
|
||||||
|
|
||||||
|
for (let i = 0; i < itemTypes.length; i++) {
|
||||||
|
if (excludeItemTypes.indexOf(itemTypes[i].name) != -1) continue;
|
||||||
|
|
||||||
|
let itemFields = data[itemTypes[i].name] = {
|
||||||
|
itemType: itemTypes[i].name
|
||||||
|
};
|
||||||
|
|
||||||
|
let fields = Zotero.ItemFields.getItemTypeFields(itemTypes[i].id);
|
||||||
|
for (let j = 0; j < fields.length; j++) {
|
||||||
|
let field = fields[j];
|
||||||
|
field = Zotero.ItemFields.getBaseIDFromTypeAndField(itemTypes[i].id, field) || field;
|
||||||
|
|
||||||
|
let name = Zotero.ItemFields.getName(field),
|
||||||
|
value;
|
||||||
|
|
||||||
|
// Use field name as field value
|
||||||
|
if (specialValues[name]) {
|
||||||
|
value = specialValues[name];
|
||||||
|
} else {
|
||||||
|
value = name.charAt(0).toUpperCase() + name.substr(1);
|
||||||
|
// Make it look nice (sentence case)
|
||||||
|
value = value.replace(/([a-z])([A-Z])/g, '$1 $2')
|
||||||
|
.replace(/ [A-Z](?![A-Z])/g, m => m.toLowerCase()); // not all-caps words
|
||||||
|
}
|
||||||
|
|
||||||
|
itemFields[name] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
let creatorTypes = Zotero.CreatorTypes.getTypesForItemType(itemTypes[i].id),
|
||||||
|
creators = itemFields.creators = [];
|
||||||
|
for (let j = 0; j < creatorTypes.length; j++) {
|
||||||
|
let typeName = creatorTypes[j].name;
|
||||||
|
creators.push({
|
||||||
|
creatorType: typeName,
|
||||||
|
firstName: typeName + 'First',
|
||||||
|
lastName: typeName + 'Last'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Populates the database with sample items
|
||||||
|
* The field values should be in the form exactly as they would appear in Zotero
|
||||||
|
*/
|
||||||
|
function populateDBWithSampleData(data) {
|
||||||
|
Zotero.DB.beginTransaction();
|
||||||
|
|
||||||
|
for (let itemName in data) {
|
||||||
|
let item = data[itemName];
|
||||||
|
let zItem = new Zotero.Item(item.itemType);
|
||||||
|
for (let itemField in item) {
|
||||||
|
if (itemField == 'itemType') continue;
|
||||||
|
|
||||||
|
if (itemField == 'creators') {
|
||||||
|
let creators = item[itemField];
|
||||||
|
for (let i=0; i<creators.length; i++) {
|
||||||
|
let creator = new Zotero.Creator();
|
||||||
|
creator.firstName = creators[i].firstName;
|
||||||
|
creator.lastName = creators[i].lastName;
|
||||||
|
creator = Zotero.Creators.get(creator.save());
|
||||||
|
|
||||||
|
zItem.setCreator(i, creator, creators[i].creatorType);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (itemField == 'tags') {
|
||||||
|
// Must save item first
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
zItem.setField(itemField, item[itemField]);
|
||||||
|
}
|
||||||
|
item.id = zItem.save();
|
||||||
|
|
||||||
|
if (item.tags && item.tags.length) {
|
||||||
|
zItem = Zotero.Items.get(item.id);
|
||||||
|
for (let i=0; i<item.tags.length; i++) {
|
||||||
|
zItem.addTag(item.tags[i].tag, item.tags[i].type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Zotero.DB.commitTransaction();
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateItemJSONData(options, currentData) {
|
||||||
|
let items = populateDBWithSampleData(loadSampleData('allTypesAndFields')),
|
||||||
|
jsonData = {};
|
||||||
|
|
||||||
|
for (let itemName in items) {
|
||||||
|
let zItem = Zotero.Items.get(items[itemName].id);
|
||||||
|
jsonData[itemName] = zItem.toJSON(options);
|
||||||
|
|
||||||
|
// Adjut accessDate so that it doesn't depend on computer time zone
|
||||||
|
// Effectively, assume that current time zone is UTC
|
||||||
|
if (jsonData[itemName].accessDate) {
|
||||||
|
let date = Zotero.Date.isoToDate(jsonData[itemName].accessDate);
|
||||||
|
date.setUTCMinutes(date.getUTCMinutes() - date.getTimezoneOffset());
|
||||||
|
jsonData[itemName].accessDate = Zotero.Date.dateToISO(date);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't replace some fields that _always_ change (e.g. item keys)
|
||||||
|
// as long as it follows expected format
|
||||||
|
// This makes it easier to generate more meaningful diffs
|
||||||
|
if (!currentData || !currentData[itemName]) continue;
|
||||||
|
|
||||||
|
for (let field in jsonData[itemName]) {
|
||||||
|
let oldVal = currentData[itemName][field];
|
||||||
|
if (!oldVal) continue;
|
||||||
|
|
||||||
|
let val = jsonData[itemName][field];
|
||||||
|
switch (field) {
|
||||||
|
case 'dateAdded':
|
||||||
|
case 'dateModified':
|
||||||
|
if (!isoDateTimeRe.test(oldVal) || !isoDateTimeRe.test(val)) continue;
|
||||||
|
break;
|
||||||
|
case 'key':
|
||||||
|
if (!zoteroObjectKeyRe.test(oldVal) || !zoteroObjectKeyRe.test(val)) continue;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonData[itemName][field] = oldVal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return jsonData;
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateCiteProcJSExportData(currentData) {
|
||||||
|
let items = populateDBWithSampleData(loadSampleData('allTypesAndFields')),
|
||||||
|
cslExportData = {};
|
||||||
|
|
||||||
|
for (let itemName in items) {
|
||||||
|
let zItem = Zotero.Items.get(items[itemName].id);
|
||||||
|
cslExportData[itemName] = Zotero.Cite.System.prototype.retrieveItem(zItem);
|
||||||
|
|
||||||
|
if (!currentData || !currentData[itemName]) continue;
|
||||||
|
|
||||||
|
// Don't replace id as long as it follows expected format
|
||||||
|
if (Number.isInteger(currentData[itemName].id)
|
||||||
|
&& Number.isInteger(cslExportData[itemName].id)
|
||||||
|
) {
|
||||||
|
cslExportData[itemName].id = currentData[itemName].id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return cslExportData;
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateTranslatorExportData(legacy, currentData) {
|
||||||
|
let items = populateDBWithSampleData(loadSampleData('allTypesAndFields')),
|
||||||
|
translatorExportData = {};
|
||||||
|
|
||||||
|
let itemGetter = new Zotero.Translate.ItemGetter();
|
||||||
|
itemGetter.legacy = !!legacy;
|
||||||
|
|
||||||
|
for (let itemName in items) {
|
||||||
|
let zItem = Zotero.Items.get(items[itemName].id);
|
||||||
|
itemGetter._itemsLeft = [zItem];
|
||||||
|
translatorExportData[itemName] = itemGetter.nextItem();
|
||||||
|
|
||||||
|
// Adjut ISO accessDate so that it doesn't depend on computer time zone
|
||||||
|
// Effectively, assume that current time zone is UTC
|
||||||
|
if (!legacy && translatorExportData[itemName].accessDate) {
|
||||||
|
let date = Zotero.Date.isoToDate(translatorExportData[itemName].accessDate);
|
||||||
|
date.setUTCMinutes(date.getUTCMinutes() - date.getTimezoneOffset());
|
||||||
|
translatorExportData[itemName].accessDate = Zotero.Date.dateToISO(date);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't replace some fields that _always_ change (e.g. item keys)
|
||||||
|
if (!currentData || !currentData[itemName]) continue;
|
||||||
|
|
||||||
|
// For simplicity, be more lenient than for item key
|
||||||
|
let uriRe = /^http:\/\/zotero\.org\/users\/local\/\w{8}\/items\/\w{8}$/;
|
||||||
|
let itemIDRe = /^\d+$/;
|
||||||
|
for (let field in translatorExportData[itemName]) {
|
||||||
|
let oldVal = currentData[itemName][field];
|
||||||
|
if (!oldVal) continue;
|
||||||
|
|
||||||
|
let val = translatorExportData[itemName][field];
|
||||||
|
switch (field) {
|
||||||
|
case 'uri':
|
||||||
|
if (!uriRe.test(oldVal) || !uriRe.test(val)) continue;
|
||||||
|
break;
|
||||||
|
case 'itemID':
|
||||||
|
if (!itemIDRe.test(oldVal) || !itemIDRe.test(val)) continue;
|
||||||
|
break;
|
||||||
|
case 'key':
|
||||||
|
if (!zoteroObjectKeyRe.test(oldVal) || !zoteroObjectKeyRe.test(val)) continue;
|
||||||
|
break;
|
||||||
|
case 'dateAdded':
|
||||||
|
case 'dateModified':
|
||||||
|
if (legacy) {
|
||||||
|
if (!sqlDateTimeRe.test(oldVal) || !sqlDateTimeRe.test(val)) continue;
|
||||||
|
} else {
|
||||||
|
if (!isoDateTimeRe.test(oldVal) || !isoDateTimeRe.test(val)) continue;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
translatorExportData[itemName][field] = oldVal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return translatorExportData;
|
||||||
}
|
}
|
|
@ -15,36 +15,44 @@ function makePath {
|
||||||
}
|
}
|
||||||
|
|
||||||
DEBUG=false
|
DEBUG=false
|
||||||
if [ "`uname`" == "Darwin" ]; then
|
if [ -z "$FX_EXECUTABLE" ]; then
|
||||||
FX_EXECUTABLE="/Applications/Firefox.app/Contents/MacOS/firefox"
|
if [ "`uname`" == "Darwin" ]; then
|
||||||
else
|
FX_EXECUTABLE="/Applications/Firefox.app/Contents/MacOS/firefox"
|
||||||
FX_EXECUTABLE="firefox"
|
else
|
||||||
|
FX_EXECUTABLE="firefox"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
FX_ARGS=""
|
FX_ARGS=""
|
||||||
|
ZOTERO_ARGS=""
|
||||||
|
|
||||||
function usage {
|
function usage {
|
||||||
cat >&2 <<DONE
|
cat >&2 <<DONE
|
||||||
Usage: $0 [-x FX_EXECUTABLE] [TESTS...]
|
Usage: $0 [option] [TESTS...]
|
||||||
Options
|
Options
|
||||||
-x FX_EXECUTABLE path to Firefox executable (default: $FX_EXECUTABLE)
|
|
||||||
-d enable debug logging
|
|
||||||
-c open JavaScript console and don't quit on completion
|
-c open JavaScript console and don't quit on completion
|
||||||
|
-d enable debug logging
|
||||||
|
-g generate test data and quit
|
||||||
|
-x FX_EXECUTABLE path to Firefox executable (default: $FX_EXECUTABLE)
|
||||||
TESTS set of tests to run (default: all)
|
TESTS set of tests to run (default: all)
|
||||||
DONE
|
DONE
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
while getopts "x:dc" opt; do
|
while getopts "x:dcg" opt; do
|
||||||
case $opt in
|
case $opt in
|
||||||
x)
|
x)
|
||||||
FX_EXECUTABLE="$OPTARG"
|
FX_EXECUTABLE="$OPTARG"
|
||||||
;;
|
;;
|
||||||
d)
|
d)
|
||||||
DEBUG=true
|
DEBUG=true
|
||||||
;;
|
;;
|
||||||
c)
|
c)
|
||||||
FX_ARGS="-jsconsole -noquit"
|
FX_ARGS="-jsconsole -noquit"
|
||||||
;;
|
;;
|
||||||
|
g)
|
||||||
|
ZOTERO_ARGS="$ZOTERO_ARGS -makeTestData"
|
||||||
|
;;
|
||||||
*)
|
*)
|
||||||
usage
|
usage
|
||||||
;;
|
;;
|
||||||
|
@ -87,13 +95,14 @@ if [ -z $IS_CYGWIN ]; then
|
||||||
echo "`MOZ_NO_REMOTE=1 NO_EM_RESTART=1 \"$FX_EXECUTABLE\" -v`"
|
echo "`MOZ_NO_REMOTE=1 NO_EM_RESTART=1 \"$FX_EXECUTABLE\" -v`"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
||||||
if [ "$TRAVIS" = true ]; then
|
if [ "$TRAVIS" = true ]; then
|
||||||
FX_ARGS="$FX_ARGS --ZoteroNoUserInput"
|
ZOTERO_ARGS="$ZOTERO_ARGS -ZoteroNoUserInput"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
makePath FX_PROFILE "$PROFILE"
|
makePath FX_PROFILE "$PROFILE"
|
||||||
MOZ_NO_REMOTE=1 NO_EM_RESTART=1 "$FX_EXECUTABLE" -profile "$FX_PROFILE" \
|
MOZ_NO_REMOTE=1 NO_EM_RESTART=1 "$FX_EXECUTABLE" -profile "$FX_PROFILE" \
|
||||||
-chrome chrome://zotero-unit/content/runtests.html -test "$TESTS" $FX_ARGS
|
-chrome chrome://zotero-unit/content/runtests.html -test "$TESTS" $ZOTERO_ARGS $FX_ARGS
|
||||||
|
|
||||||
# Check for success
|
# Check for success
|
||||||
test -e "$PROFILE/success"
|
test -e "$PROFILE/success"
|
||||||
|
|
1366
test/tests/data/allTypesAndFields.js
Normal file
1366
test/tests/data/allTypesAndFields.js
Normal file
File diff suppressed because it is too large
Load diff
1668
test/tests/data/citeProcJSExport.js
Normal file
1668
test/tests/data/citeProcJSExport.js
Normal file
File diff suppressed because it is too large
Load diff
BIN
test/tests/data/empty.pdf
Normal file
BIN
test/tests/data/empty.pdf
Normal file
Binary file not shown.
1604
test/tests/data/itemJSON.js
Normal file
1604
test/tests/data/itemJSON.js
Normal file
File diff suppressed because it is too large
Load diff
55
test/tests/data/journalArticle.js
Normal file
55
test/tests/data/journalArticle.js
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
{
|
||||||
|
"journalArticle": {
|
||||||
|
"DOI": "10.1234/example.doi",
|
||||||
|
"ISSN": "1234-5679",
|
||||||
|
"abstractNote": "Abstract note",
|
||||||
|
"accessDate": "1997-06-13 23:59:58",
|
||||||
|
"archive": "Archive",
|
||||||
|
"archiveLocation": "Archive location",
|
||||||
|
"callNumber": "Call number",
|
||||||
|
"creators": [
|
||||||
|
{
|
||||||
|
"creatorType": "author",
|
||||||
|
"firstName": "authorFirst",
|
||||||
|
"lastName": "authorLast"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"creatorType": "contributor",
|
||||||
|
"firstName": "contributorFirst",
|
||||||
|
"lastName": "contributorLast"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"creatorType": "editor",
|
||||||
|
"firstName": "editorFirst",
|
||||||
|
"lastName": "editorLast"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"creatorType": "reviewedAuthor",
|
||||||
|
"firstName": "reviewedAuthorFirst",
|
||||||
|
"lastName": "reviewedAuthorLast"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"creatorType": "translator",
|
||||||
|
"firstName": "translatorFirst",
|
||||||
|
"lastName": "translatorLast"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"date": "1999-12-31",
|
||||||
|
"extra": "Extra",
|
||||||
|
"issue": 5,
|
||||||
|
"itemType": "journalArticle",
|
||||||
|
"journalAbbreviation": "Journal abbreviation",
|
||||||
|
"language": "en-US",
|
||||||
|
"libraryCatalog": "Library catalog",
|
||||||
|
"pages": "1-10",
|
||||||
|
"publicationTitle": "Publication title",
|
||||||
|
"rights": "Rights",
|
||||||
|
"series": "Series",
|
||||||
|
"seriesText": "Series text",
|
||||||
|
"seriesTitle": "Series title",
|
||||||
|
"shortTitle": "Short title",
|
||||||
|
"title": "Title",
|
||||||
|
"url": "http://www.example.com",
|
||||||
|
"volume": 6
|
||||||
|
}
|
||||||
|
}
|
1672
test/tests/data/translatorExport.js
Normal file
1672
test/tests/data/translatorExport.js
Normal file
File diff suppressed because it is too large
Load diff
2475
test/tests/data/translatorExportLegacy.js
Normal file
2475
test/tests/data/translatorExportLegacy.js
Normal file
File diff suppressed because it is too large
Load diff
|
@ -9,4 +9,184 @@ describe("Support Functions for Unit Testing", function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
describe("loadSampleData", function() {
|
||||||
|
it("should load data from file", function() {
|
||||||
|
let data = loadSampleData('journalArticle');
|
||||||
|
assert.isObject(data, 'loaded data object');
|
||||||
|
assert.isNotNull(data);
|
||||||
|
assert.isAbove(Object.keys(data).length, 0, 'data object is not empty');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe("populateDBWithSampleData", function() {
|
||||||
|
it("should populate database with data", function() {
|
||||||
|
let data = loadSampleData('journalArticle');
|
||||||
|
populateDBWithSampleData(data);
|
||||||
|
|
||||||
|
let skipFields = ['id', 'itemType', 'creators']; // Special comparisons
|
||||||
|
|
||||||
|
for (let itemName in data) {
|
||||||
|
let item = data[itemName];
|
||||||
|
assert.isAbove(item.id, 0, 'assigned new item ID');
|
||||||
|
|
||||||
|
let zItem = Zotero.Items.get(item.id);
|
||||||
|
assert.ok(zItem, 'inserted item into database');
|
||||||
|
|
||||||
|
// Compare item type
|
||||||
|
assert.equal(item.itemType, Zotero.ItemTypes.getName(zItem.itemTypeID), 'inserted item has the same item type');
|
||||||
|
|
||||||
|
// Compare simple properties
|
||||||
|
for (let prop in item) {
|
||||||
|
if (skipFields.indexOf(prop) != -1) continue;
|
||||||
|
|
||||||
|
// Using base-mapped fields
|
||||||
|
assert.equal(item[prop], zItem.getField(prop, false, true), 'inserted item property has the same value as sample data');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.creators) {
|
||||||
|
// Compare creators
|
||||||
|
for (let i=0; i<item.creators.length; i++) {
|
||||||
|
let creator = item.creators[i];
|
||||||
|
let zCreator = zItem.getCreator(i);
|
||||||
|
assert.ok(zCreator, 'creator was added to item');
|
||||||
|
assert.equal(creator.firstName, zCreator.ref.firstName, 'first names match');
|
||||||
|
assert.equal(creator.lastName, zCreator.ref.lastName, 'last names match');
|
||||||
|
assert.equal(creator.creatorType, Zotero.CreatorTypes.getName(zCreator.creatorTypeID), 'creator types match');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
it("should populate items with tags", function() {
|
||||||
|
let data = populateDBWithSampleData({
|
||||||
|
itemWithTags: {
|
||||||
|
itemType: "journalArticle",
|
||||||
|
tags: [
|
||||||
|
{ tag: "automatic tag", type: 0 },
|
||||||
|
{ tag: "manual tag", type: 1}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let zItem = Zotero.Items.get(data.itemWithTags.id);
|
||||||
|
assert.ok(zItem, 'inserted item with tags into database');
|
||||||
|
|
||||||
|
let tags = data.itemWithTags.tags;
|
||||||
|
for (let i=0; i<tags.length; i++) {
|
||||||
|
let tagID = Zotero.Tags.getID(tags[i].tag, tags[i].type);
|
||||||
|
assert.ok(tagID, '"' + tags[i].tag + '" tag was inserted into the database');
|
||||||
|
assert.ok(zItem.hasTag(tagID), '"' + tags[i].tag + '" tag was assigned to item');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe("generateAllTypesAndFieldsData", function() {
|
||||||
|
it("should generate all types and fields data", function() {
|
||||||
|
let data = generateAllTypesAndFieldsData();
|
||||||
|
assert.isObject(data, 'created data object');
|
||||||
|
assert.isNotNull(data);
|
||||||
|
assert.isAbove(Object.keys(data).length, 0, 'data object is not empty');
|
||||||
|
});
|
||||||
|
it("all types and fields sample data should be up to date", function() {
|
||||||
|
assert.deepEqual(loadSampleData('allTypesAndFields'), generateAllTypesAndFieldsData());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe("generateItemJSONData", function() {
|
||||||
|
it("item JSON data should be up to date", function() {
|
||||||
|
let oldData = loadSampleData('itemJSON'),
|
||||||
|
newData = generateItemJSONData();
|
||||||
|
|
||||||
|
assert.isObject(newData, 'created data object');
|
||||||
|
assert.isNotNull(newData);
|
||||||
|
assert.isAbove(Object.keys(newData).length, 0, 'data object is not empty');
|
||||||
|
|
||||||
|
// Ignore data that is not stable, but make sure it is set
|
||||||
|
let ignoreFields = ['dateAdded', 'dateModified', 'key'];
|
||||||
|
for (let itemName in oldData) {
|
||||||
|
for (let i=0; i<ignoreFields.length; i++) {
|
||||||
|
let field = ignoreFields[i]
|
||||||
|
if (oldData[itemName][field] !== undefined) {
|
||||||
|
assert.isDefined(newData[itemName][field], field + ' is set');
|
||||||
|
delete oldData[itemName][field];
|
||||||
|
delete newData[itemName][field];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.deepEqual(oldData, newData);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe("generateCiteProcJSExportData", function() {
|
||||||
|
let citeURL = Zotero.Prefs.get("export.citePaperJournalArticleURL");
|
||||||
|
before(function () {
|
||||||
|
Zotero.Prefs.set("export.citePaperJournalArticleURL", true);
|
||||||
|
});
|
||||||
|
after(function() {
|
||||||
|
Zotero.Prefs.set("export.citePaperJournalArticleURL", citeURL);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("all citeproc-js export data should be up to date", function() {
|
||||||
|
let oldData = loadSampleData('citeProcJSExport'),
|
||||||
|
newData = generateCiteProcJSExportData();
|
||||||
|
|
||||||
|
assert.isObject(newData, 'created data object');
|
||||||
|
assert.isNotNull(newData);
|
||||||
|
assert.isAbove(Object.keys(newData).length, 0, 'citeproc-js export object is not empty');
|
||||||
|
|
||||||
|
// Ignore item ID
|
||||||
|
for (let itemName in oldData) {
|
||||||
|
delete oldData[itemName].id;
|
||||||
|
}
|
||||||
|
for (let itemName in newData) {
|
||||||
|
delete newData[itemName].id;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.deepEqual(oldData, newData, 'citeproc-js export data has not changed');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe("generateTranslatorExportData", function() {
|
||||||
|
it("legacy mode data should be up to date", function() {
|
||||||
|
let oldData = loadSampleData('translatorExportLegacy'),
|
||||||
|
newData = generateTranslatorExportData(true);
|
||||||
|
|
||||||
|
assert.isObject(newData, 'created data object');
|
||||||
|
assert.isNotNull(newData);
|
||||||
|
assert.isAbove(Object.keys(newData).length, 0, 'translator export object is not empty');
|
||||||
|
|
||||||
|
// Ignore data that is not stable, but make sure it is set
|
||||||
|
let ignoreFields = ['itemID', 'dateAdded', 'dateModified', 'uri', 'key'];
|
||||||
|
for (let itemName in oldData) {
|
||||||
|
for (let i=0; i<ignoreFields.length; i++) {
|
||||||
|
let field = ignoreFields[i]
|
||||||
|
if (oldData[itemName][field] !== undefined) {
|
||||||
|
assert.isDefined(newData[itemName][field], field + ' is set');
|
||||||
|
delete oldData[itemName][field];
|
||||||
|
delete newData[itemName][field];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.deepEqual(oldData, newData, 'translator export data has not changed');
|
||||||
|
});
|
||||||
|
it("data should be up to date", function() {
|
||||||
|
let oldData = loadSampleData('translatorExport'),
|
||||||
|
newData = generateTranslatorExportData();
|
||||||
|
|
||||||
|
assert.isObject(newData, 'created data object');
|
||||||
|
assert.isNotNull(newData);
|
||||||
|
assert.isAbove(Object.keys(newData).length, 0, 'translator export object is not empty');
|
||||||
|
|
||||||
|
// Ignore data that is not stable, but make sure it is set
|
||||||
|
let ignoreFields = ['dateAdded', 'dateModified', 'uri'];
|
||||||
|
for (let itemName in oldData) {
|
||||||
|
for (let i=0; i<ignoreFields.length; i++) {
|
||||||
|
let field = ignoreFields[i]
|
||||||
|
if (oldData[itemName][field] !== undefined) {
|
||||||
|
assert.isDefined(newData[itemName][field], field + ' is set');
|
||||||
|
delete oldData[itemName][field];
|
||||||
|
delete newData[itemName][field];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.deepEqual(oldData, newData, 'translator export data has not changed');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
577
test/tests/translateTest.js
Normal file
577
test/tests/translateTest.js
Normal file
|
@ -0,0 +1,577 @@
|
||||||
|
Components.utils.import("resource://gre/modules/osfile.jsm");
|
||||||
|
|
||||||
|
describe("Zotero.Translate.ItemGetter", function() {
|
||||||
|
describe("nextItem", function() {
|
||||||
|
it('should return false for an empty database', function() {
|
||||||
|
let getter = new Zotero.Translate.ItemGetter();
|
||||||
|
assert.isFalse(getter.nextItem());
|
||||||
|
});
|
||||||
|
it('should return items in order they are supplied', function() {
|
||||||
|
let getter = new Zotero.Translate.ItemGetter();
|
||||||
|
|
||||||
|
Zotero.DB.beginTransaction();
|
||||||
|
|
||||||
|
let itemIDs = [
|
||||||
|
(new Zotero.Item('journalArticle')).save(),
|
||||||
|
(new Zotero.Item('book')).save()
|
||||||
|
];
|
||||||
|
|
||||||
|
let items = [ Zotero.Items.get(itemIDs[0]), Zotero.Items.get(itemIDs[1]) ];
|
||||||
|
let itemURIs = items.map(i => Zotero.URI.getItemURI(i));
|
||||||
|
|
||||||
|
Zotero.DB.commitTransaction();
|
||||||
|
|
||||||
|
getter._itemsLeft = items;
|
||||||
|
|
||||||
|
assert.equal(getter.nextItem().uri, itemURIs[0], 'first item comes out first');
|
||||||
|
assert.equal(getter.nextItem().uri, itemURIs[1], 'second item comes out second');
|
||||||
|
assert.isFalse(getter.nextItem(), 'end of item queue');
|
||||||
|
});
|
||||||
|
it('should return items with tags in expected format', function() {
|
||||||
|
let getter = new Zotero.Translate.ItemGetter();
|
||||||
|
|
||||||
|
Zotero.DB.beginTransaction();
|
||||||
|
|
||||||
|
let itemWithAutomaticTag = Zotero.Items.get((new Zotero.Item('journalArticle')).save());
|
||||||
|
itemWithAutomaticTag.addTag('automatic tag', 0);
|
||||||
|
|
||||||
|
let itemWithManualTag = Zotero.Items.get((new Zotero.Item('journalArticle')).save());
|
||||||
|
itemWithManualTag.addTag('manual tag', 1);
|
||||||
|
|
||||||
|
let itemWithMultipleTags = Zotero.Items.get((new Zotero.Item('journalArticle')).save());
|
||||||
|
itemWithMultipleTags.addTag('tag1', 0);
|
||||||
|
itemWithMultipleTags.addTag('tag2', 1);
|
||||||
|
|
||||||
|
Zotero.DB.commitTransaction();
|
||||||
|
|
||||||
|
let legacyMode = [false, true];
|
||||||
|
for (let i=0; i<legacyMode.length; i++) {
|
||||||
|
getter._itemsLeft = [itemWithAutomaticTag, itemWithManualTag, itemWithMultipleTags];
|
||||||
|
getter.legacy = legacyMode[i];
|
||||||
|
let suffix = legacyMode[i] ? ' in legacy mode' : '';
|
||||||
|
|
||||||
|
// itemWithAutomaticTag
|
||||||
|
let translatorItem = getter.nextItem();
|
||||||
|
assert.isArray(translatorItem.tags, 'item contains automatic tags in an array' + suffix);
|
||||||
|
assert.isObject(translatorItem.tags[0], 'automatic tag is an object' + suffix);
|
||||||
|
assert.equal(translatorItem.tags[0].tag, 'automatic tag', 'automatic tag name provided as "tag" property' + suffix);
|
||||||
|
if (legacyMode[i]) {
|
||||||
|
assert.equal(translatorItem.tags[0].type, 0, 'automatic tag "type" is 0' + suffix);
|
||||||
|
} else {
|
||||||
|
assert.isUndefined(translatorItem.tags[0].type, '"type" is undefined for automatic tag' + suffix);
|
||||||
|
}
|
||||||
|
|
||||||
|
// itemWithManualTag
|
||||||
|
translatorItem = getter.nextItem();
|
||||||
|
assert.isArray(translatorItem.tags, 'item contains manual tags in an array' + suffix);
|
||||||
|
assert.isObject(translatorItem.tags[0], 'manual tag is an object' + suffix);
|
||||||
|
assert.equal(translatorItem.tags[0].tag, 'manual tag', 'manual tag name provided as "tag" property' + suffix);
|
||||||
|
assert.equal(translatorItem.tags[0].type, 1, 'manual tag "type" is 1' + suffix);
|
||||||
|
|
||||||
|
// itemWithMultipleTags
|
||||||
|
translatorItem = getter.nextItem();
|
||||||
|
assert.isArray(translatorItem.tags, 'item contains multiple tags in an array' + suffix);
|
||||||
|
assert.lengthOf(translatorItem.tags, 2, 'expected number of tags returned' + suffix);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
it('should return item collections in expected format', function() {
|
||||||
|
let getter = new Zotero.Translate.ItemGetter();
|
||||||
|
|
||||||
|
Zotero.DB.beginTransaction();
|
||||||
|
|
||||||
|
let items = getter._itemsLeft = [
|
||||||
|
Zotero.Items.get((new Zotero.Item('journalArticle')).save()), // Not in collection
|
||||||
|
Zotero.Items.get((new Zotero.Item('journalArticle')).save()), // In a single collection
|
||||||
|
Zotero.Items.get((new Zotero.Item('journalArticle')).save()), //In two collections
|
||||||
|
Zotero.Items.get((new Zotero.Item('journalArticle')).save()) // In a nested collection
|
||||||
|
];
|
||||||
|
|
||||||
|
let collections = [
|
||||||
|
Zotero.Collections.add('test1'),
|
||||||
|
Zotero.Collections.add('test2')
|
||||||
|
];
|
||||||
|
collections.push(Zotero.Collections.add('subTest1', collections[0].id));
|
||||||
|
collections.push(Zotero.Collections.add('subTest2', collections[1].id));
|
||||||
|
|
||||||
|
collections[0].addItems([items[1].id, items[2].id]);
|
||||||
|
collections[1].addItem(items[2].id);
|
||||||
|
collections[2].addItem(items[3].id);
|
||||||
|
|
||||||
|
Zotero.DB.commitTransaction();
|
||||||
|
|
||||||
|
let translatorItem = getter.nextItem();
|
||||||
|
assert.isArray(translatorItem.collections, 'item in library root has a collections array');
|
||||||
|
assert.equal(translatorItem.collections.length, 0, 'item in library root does not list any collections');
|
||||||
|
|
||||||
|
translatorItem = getter.nextItem();
|
||||||
|
assert.isArray(translatorItem.collections, 'item in a single collection has a collections array');
|
||||||
|
assert.equal(translatorItem.collections.length, 1, 'item in a single collection lists one collection');
|
||||||
|
assert.equal(translatorItem.collections[0], collections[0].key, 'item in a single collection identifies correct collection');
|
||||||
|
|
||||||
|
translatorItem = getter.nextItem();
|
||||||
|
assert.isArray(translatorItem.collections, 'item in two collections has a collections array');
|
||||||
|
assert.equal(translatorItem.collections.length, 2, 'item in two collections lists two collections');
|
||||||
|
assert.deepEqual(
|
||||||
|
translatorItem.collections.sort(),
|
||||||
|
[collections[0].key, collections[1].key].sort(),
|
||||||
|
'item in two collections identifies correct collections'
|
||||||
|
);
|
||||||
|
|
||||||
|
translatorItem = getter.nextItem();
|
||||||
|
assert.isArray(translatorItem.collections, 'item in a nested collection has a collections array');
|
||||||
|
assert.equal(translatorItem.collections.length, 1, 'item in a single nested collection lists one collection');
|
||||||
|
assert.equal(translatorItem.collections[0], collections[2].key, 'item in a single collection identifies correct collection');
|
||||||
|
});
|
||||||
|
it('should return item relations in expected format', function() {
|
||||||
|
let getter = new Zotero.Translate.ItemGetter();
|
||||||
|
|
||||||
|
Zotero.DB.beginTransaction();
|
||||||
|
|
||||||
|
let items = [
|
||||||
|
Zotero.Items.get((new Zotero.Item('journalArticle')).save()), // Item with no relations
|
||||||
|
|
||||||
|
Zotero.Items.get((new Zotero.Item('journalArticle')).save()), // Relation set on this item
|
||||||
|
Zotero.Items.get((new Zotero.Item('journalArticle')).save()), // To this item
|
||||||
|
|
||||||
|
Zotero.Items.get((new Zotero.Item('journalArticle')).save()), // This item is related to two items below
|
||||||
|
Zotero.Items.get((new Zotero.Item('journalArticle')).save()), // But this item is not related to the item below
|
||||||
|
Zotero.Items.get((new Zotero.Item('journalArticle')).save())
|
||||||
|
];
|
||||||
|
|
||||||
|
items[1].addRelatedItem(items[2].id);
|
||||||
|
items[1].save();
|
||||||
|
|
||||||
|
items[3].addRelatedItem(items[4].id);
|
||||||
|
items[3].addRelatedItem(items[5].id);
|
||||||
|
items[3].save();
|
||||||
|
|
||||||
|
Zotero.DB.commitTransaction();
|
||||||
|
|
||||||
|
getter._itemsLeft = items.slice();
|
||||||
|
|
||||||
|
let translatorItem = getter.nextItem();
|
||||||
|
assert.isObject(translatorItem.relations, 'item with no relations has a relations object');
|
||||||
|
assert.equal(Object.keys(translatorItem.relations).length, 0, 'item with no relations does not list any relations');
|
||||||
|
|
||||||
|
translatorItem = getter.nextItem();
|
||||||
|
assert.isObject(translatorItem.relations, 'item that is the subject of a single relation has a relations object');
|
||||||
|
assert.equal(Object.keys(translatorItem.relations).length, 1, 'item that is the subject of a single relation list one relations predicate');
|
||||||
|
assert.isDefined(translatorItem.relations['dc:relation'], 'item that is the subject of a single relation uses "dc:relation" as the predicate');
|
||||||
|
assert.isString(translatorItem.relations['dc:relation'], 'item that is the subject of a single relation lists "dc:relation" object as a string');
|
||||||
|
assert.equal(translatorItem.relations['dc:relation'], Zotero.URI.getItemURI(items[2]), 'item that is the subject of a single relation identifies correct object URI');
|
||||||
|
|
||||||
|
translatorItem = getter.nextItem();
|
||||||
|
assert.isObject(translatorItem.relations, 'item that is the object of a single relation has a relations object');
|
||||||
|
assert.equal(Object.keys(translatorItem.relations).length, 1, 'item that is the object of a single relation list one relations predicate');
|
||||||
|
assert.isDefined(translatorItem.relations['dc:relation'], 'item that is the object of a single relation uses "dc:relation" as the predicate');
|
||||||
|
assert.isString(translatorItem.relations['dc:relation'], 'item that is the object of a single relation lists "dc:relation" object as a string');
|
||||||
|
assert.equal(translatorItem.relations['dc:relation'], Zotero.URI.getItemURI(items[1]), 'item that is the object of a single relation identifies correct subject URI');
|
||||||
|
|
||||||
|
translatorItem = getter.nextItem();
|
||||||
|
assert.isObject(translatorItem.relations, 'item that is the subject of two relations has a relations object');
|
||||||
|
assert.equal(Object.keys(translatorItem.relations).length, 1, 'item that is the subject of two relations list one relations predicate');
|
||||||
|
assert.isDefined(translatorItem.relations['dc:relation'], 'item that is the subject of two relations uses "dc:relation" as the predicate');
|
||||||
|
assert.isArray(translatorItem.relations['dc:relation'], 'item that is the subject of two relations lists "dc:relation" object as an array');
|
||||||
|
assert.equal(translatorItem.relations['dc:relation'].length, 2, 'item that is the subject of two relations lists two relations in the "dc:relation" array');
|
||||||
|
assert.deepEqual(translatorItem.relations['dc:relation'].sort(),
|
||||||
|
[Zotero.URI.getItemURI(items[4]), Zotero.URI.getItemURI(items[5])].sort(),
|
||||||
|
'item that is the subject of two relations identifies correct object URIs'
|
||||||
|
);
|
||||||
|
|
||||||
|
translatorItem = getter.nextItem();
|
||||||
|
assert.isObject(translatorItem.relations, 'item that is the object of one relation from item with two relations has a relations object');
|
||||||
|
assert.equal(Object.keys(translatorItem.relations).length, 1, 'item that is the object of one relation from item with two relations list one relations predicate');
|
||||||
|
assert.isDefined(translatorItem.relations['dc:relation'], 'item that is the object of one relation from item with two relations uses "dc:relation" as the predicate');
|
||||||
|
assert.isString(translatorItem.relations['dc:relation'], 'item that is the object of one relation from item with two relations lists "dc:relation" object as a string');
|
||||||
|
assert.equal(translatorItem.relations['dc:relation'], Zotero.URI.getItemURI(items[3]), 'item that is the object of one relation from item with two relations identifies correct subject URI');
|
||||||
|
});
|
||||||
|
it('should return standalone note in expected format', function () {
|
||||||
|
let relatedItem = Zotero.Items.get((new Zotero.Item('journalArticle')).save());
|
||||||
|
|
||||||
|
Zotero.DB.beginTransaction();
|
||||||
|
|
||||||
|
let note = new Zotero.Item('note');
|
||||||
|
note.setNote('Note');
|
||||||
|
note = Zotero.Items.get(note.save());
|
||||||
|
|
||||||
|
note.addRelatedItem(relatedItem.id);
|
||||||
|
note.save();
|
||||||
|
|
||||||
|
let collection = Zotero.Collections.add('test');
|
||||||
|
collection.addItem(note.id);
|
||||||
|
|
||||||
|
note.addTag('automaticTag', 0);
|
||||||
|
note.addTag('manualTag', 1);
|
||||||
|
|
||||||
|
Zotero.DB.commitTransaction();
|
||||||
|
|
||||||
|
let legacyMode = [false, true];
|
||||||
|
for (let i=0; i<legacyMode.length; i++) {
|
||||||
|
let getter = new Zotero.Translate.ItemGetter();
|
||||||
|
getter._itemsLeft = [note];
|
||||||
|
let legacy = getter.legacy = legacyMode[i];
|
||||||
|
let suffix = legacy ? ' in legacy mode' : '';
|
||||||
|
|
||||||
|
let translatorNote = getter.nextItem();
|
||||||
|
assert.isDefined(translatorNote, 'returns standalone note' + suffix);
|
||||||
|
assert.equal(translatorNote.itemType, 'note', 'itemType is correct' + suffix);
|
||||||
|
assert.equal(translatorNote.note, 'Note', 'note is correct' + suffix);
|
||||||
|
|
||||||
|
assert.isString(translatorNote.dateAdded, 'dateAdded is string' + suffix);
|
||||||
|
assert.isString(translatorNote.dateModified, 'dateModified is string' + suffix);
|
||||||
|
|
||||||
|
if (legacy) {
|
||||||
|
assert.isTrue(sqlDateTimeRe.test(translatorNote.dateAdded), 'dateAdded is in correct format' + suffix);
|
||||||
|
assert.isTrue(sqlDateTimeRe.test(translatorNote.dateModified), 'dateModified is in correct format' + suffix);
|
||||||
|
|
||||||
|
assert.isNumber(translatorNote.itemID, 'itemID is set' + suffix);
|
||||||
|
assert.isString(translatorNote.key, 'key is set' + suffix);
|
||||||
|
} else {
|
||||||
|
assert.isTrue(isoDateTimeRe.test(translatorNote.dateAdded), 'dateAdded is in correct format' + suffix);
|
||||||
|
assert.isTrue(isoDateTimeRe.test(translatorNote.dateModified), 'dateModified is in correct format' + suffix);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tags
|
||||||
|
assert.isArray(translatorNote.tags, 'contains tags as array' + suffix);
|
||||||
|
assert.equal(translatorNote.tags.length, 2, 'contains correct number of tags' + suffix);
|
||||||
|
let possibleTags = [
|
||||||
|
{ tag: 'automaticTag', type: 0 },
|
||||||
|
{ tag: 'manualTag', type: 1 }
|
||||||
|
];
|
||||||
|
for (let i=0; i<possibleTags.length; i++) {
|
||||||
|
let match = false;
|
||||||
|
for (let j=0; j<translatorNote.tags.length; j++) {
|
||||||
|
if (possibleTags[i].tag == translatorNote.tags[j].tag) {
|
||||||
|
let type = possibleTags[i].type;
|
||||||
|
if (!legacy && type == 0) type = undefined;
|
||||||
|
|
||||||
|
assert.equal(translatorNote.tags[j].type, type, possibleTags[i].tag + ' tag is correct' + suffix);
|
||||||
|
match = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert.isTrue(match, 'has ' + possibleTags[i].tag + ' tag ' + suffix);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Relations
|
||||||
|
assert.isObject(translatorNote.relations, 'has relations as object' + suffix);
|
||||||
|
assert.equal(translatorNote.relations['dc:relation'], Zotero.URI.getItemURI(relatedItem), 'relation is correct' + suffix);
|
||||||
|
/** TODO: test other relations and multiple relations per predicate (should be an array) **/
|
||||||
|
|
||||||
|
if (!legacy) {
|
||||||
|
// Collections
|
||||||
|
assert.isArray(translatorNote.collections, 'has a collections array' + suffix);
|
||||||
|
assert.equal(translatorNote.collections.length, 1, 'lists one collection' + suffix);
|
||||||
|
assert.equal(translatorNote.collections[0], collection.key, 'identifies correct collection' + suffix);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
it('should return attached note in expected format', function () {
|
||||||
|
Zotero.DB.beginTransaction();
|
||||||
|
|
||||||
|
let relatedItem = Zotero.Items.get((new Zotero.Item('journalArticle')).save());
|
||||||
|
|
||||||
|
let items = [
|
||||||
|
Zotero.Items.get((new Zotero.Item('journalArticle')).save()),
|
||||||
|
Zotero.Items.get((new Zotero.Item('journalArticle')).save())
|
||||||
|
];
|
||||||
|
|
||||||
|
let collection = Zotero.Collections.add('test');
|
||||||
|
collection.addItem(items[0].id);
|
||||||
|
collection.addItem(items[1].id);
|
||||||
|
|
||||||
|
let note = new Zotero.Item('note');
|
||||||
|
note.setNote('Note');
|
||||||
|
note = Zotero.Items.get(note.save());
|
||||||
|
|
||||||
|
note.addRelatedItem(relatedItem.id);
|
||||||
|
note.save();
|
||||||
|
|
||||||
|
note.addTag('automaticTag', 0);
|
||||||
|
note.addTag('manualTag', 1);
|
||||||
|
|
||||||
|
Zotero.DB.commitTransaction();
|
||||||
|
|
||||||
|
let legacyMode = [false, true];
|
||||||
|
for (let i=0; i<legacyMode.length; i++) {
|
||||||
|
let item = items[i];
|
||||||
|
|
||||||
|
let getter = new Zotero.Translate.ItemGetter();
|
||||||
|
getter._itemsLeft = [item];
|
||||||
|
let legacy = getter.legacy = legacyMode[i];
|
||||||
|
let suffix = legacy ? ' in legacy mode' : '';
|
||||||
|
|
||||||
|
let translatorItem = getter.nextItem();
|
||||||
|
assert.isArray(translatorItem.notes, 'item with no notes contains notes array' + suffix);
|
||||||
|
assert.equal(translatorItem.notes.length, 0, 'item with no notes contains empty notes array' + suffix);
|
||||||
|
|
||||||
|
note.setSource(item.id);
|
||||||
|
note.save();
|
||||||
|
|
||||||
|
getter = new Zotero.Translate.ItemGetter();
|
||||||
|
getter._itemsLeft = [item];
|
||||||
|
getter.legacy = legacy;
|
||||||
|
|
||||||
|
translatorItem = getter.nextItem();
|
||||||
|
assert.isArray(translatorItem.notes, 'item with no notes contains notes array' + suffix);
|
||||||
|
assert.equal(translatorItem.notes.length, 1, 'item with one note contains array with one note' + suffix);
|
||||||
|
|
||||||
|
let translatorNote = translatorItem.notes[0];
|
||||||
|
assert.equal(translatorNote.itemType, 'note', 'itemType is correct' + suffix);
|
||||||
|
assert.equal(translatorNote.note, 'Note', 'note is correct' + suffix);
|
||||||
|
|
||||||
|
assert.isString(translatorNote.dateAdded, 'dateAdded is string' + suffix);
|
||||||
|
assert.isString(translatorNote.dateModified, 'dateModified is string' + suffix);
|
||||||
|
|
||||||
|
if (legacy) {
|
||||||
|
assert.isTrue(sqlDateTimeRe.test(translatorNote.dateAdded), 'dateAdded is in correct format' + suffix);
|
||||||
|
assert.isTrue(sqlDateTimeRe.test(translatorNote.dateModified), 'dateModified is in correct format' + suffix);
|
||||||
|
|
||||||
|
assert.isNumber(translatorNote.itemID, 'itemID is set' + suffix);
|
||||||
|
assert.isString(translatorNote.key, 'key is set' + suffix);
|
||||||
|
} else {
|
||||||
|
assert.isTrue(isoDateTimeRe.test(translatorNote.dateAdded), 'dateAdded is in correct format' + suffix);
|
||||||
|
assert.isTrue(isoDateTimeRe.test(translatorNote.dateModified), 'dateModified is in correct format' + suffix);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tags
|
||||||
|
assert.isArray(translatorNote.tags, 'contains tags as array' + suffix);
|
||||||
|
assert.equal(translatorNote.tags.length, 2, 'contains correct number of tags' + suffix);
|
||||||
|
let possibleTags = [
|
||||||
|
{ tag: 'automaticTag', type: 0 },
|
||||||
|
{ tag: 'manualTag', type: 1 }
|
||||||
|
];
|
||||||
|
for (let i=0; i<possibleTags.length; i++) {
|
||||||
|
let match = false;
|
||||||
|
for (let j=0; j<translatorNote.tags.length; j++) {
|
||||||
|
if (possibleTags[i].tag == translatorNote.tags[j].tag) {
|
||||||
|
let type = possibleTags[i].type;
|
||||||
|
if (!legacy && type == 0) type = undefined;
|
||||||
|
|
||||||
|
assert.equal(translatorNote.tags[j].type, type, possibleTags[i].tag + ' tag is correct' + suffix);
|
||||||
|
match = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert.isTrue(match, 'has ' + possibleTags[i].tag + ' tag ' + suffix);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Relations
|
||||||
|
assert.isObject(translatorNote.relations, 'has relations as object' + suffix);
|
||||||
|
assert.equal(translatorNote.relations['dc:relation'], Zotero.URI.getItemURI(relatedItem), 'relation is correct' + suffix);
|
||||||
|
/** TODO: test other relations and multiple relations per predicate (should be an array) **/
|
||||||
|
|
||||||
|
if (!legacy) {
|
||||||
|
// Collections
|
||||||
|
assert.isArray(translatorNote.collections, 'has a collections array' + suffix);
|
||||||
|
assert.equal(translatorNote.collections.length, 0, 'does not list collections for parent item' + suffix);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
it('should return stored/linked file and URI attachments in expected format', Q.async(function () {
|
||||||
|
let file = getTestDataDirectory();
|
||||||
|
file.append("empty.pdf");
|
||||||
|
|
||||||
|
Zotero.DB.beginTransaction();
|
||||||
|
|
||||||
|
let item = Zotero.Items.get((new Zotero.Item('journalArticle')).save());
|
||||||
|
let relatedItem = Zotero.Items.get((new Zotero.Item('journalArticle')).save());
|
||||||
|
|
||||||
|
// Attachment items
|
||||||
|
let attachments = [
|
||||||
|
Zotero.Items.get(Zotero.Attachments.importFromFile(file)), // Standalone stored file
|
||||||
|
Zotero.Items.get(Zotero.Attachments.linkFromFile(file)), // Standalone link to file
|
||||||
|
Zotero.Items.get(Zotero.Attachments.importFromFile(file, item.id)), // Attached stored file
|
||||||
|
Zotero.Items.get(Zotero.Attachments.linkFromFile(file, item.id)), // Attached link to file
|
||||||
|
Zotero.Items.get(Zotero.Attachments.linkFromURL('http://example.com', item.id, 'application/pdf', 'empty.pdf')) // Attached link to URL
|
||||||
|
];
|
||||||
|
|
||||||
|
// Make sure all fields are populated
|
||||||
|
for (let i=0; i<attachments.length; i++) {
|
||||||
|
let attachment = attachments[i];
|
||||||
|
attachment.setField('accessDate', '2001-02-03 12:13:14');
|
||||||
|
attachment.attachmentCharset = Zotero.CharacterSets.getID('utf-8');
|
||||||
|
attachment.setField('url', 'http://example.com');
|
||||||
|
attachment.setNote('note');
|
||||||
|
|
||||||
|
attachment.addTag('automaticTag', 0);
|
||||||
|
attachment.addTag('manualTag', 1);
|
||||||
|
|
||||||
|
attachment.addRelatedItem(relatedItem.id);
|
||||||
|
|
||||||
|
attachment.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
Zotero.DB.commitTransaction();
|
||||||
|
|
||||||
|
let items = [ attachments[0], attachments[1], item ]; // Standalone attachments and item with child attachments
|
||||||
|
|
||||||
|
// Run tests
|
||||||
|
let legacyMode = [false, true];
|
||||||
|
for (let i=0; i<legacyMode.length; i++) {
|
||||||
|
let getter = new Zotero.Translate.ItemGetter();
|
||||||
|
getter._itemsLeft = items.slice();
|
||||||
|
|
||||||
|
let exportDir = yield getTempDirectory();
|
||||||
|
getter._exportFileDirectory = Components.classes["@mozilla.org/file/local;1"]
|
||||||
|
.createInstance(Components.interfaces.nsILocalFile);
|
||||||
|
getter._exportFileDirectory.initWithPath(exportDir);
|
||||||
|
|
||||||
|
let legacy = getter.legacy = legacyMode[i];
|
||||||
|
let suffix = legacy ? ' in legacy mode' : '';
|
||||||
|
|
||||||
|
// Gather all standalone and child attachments into a single array,
|
||||||
|
// since tests are mostly the same
|
||||||
|
let translatorAttachments = [], translatorItem;
|
||||||
|
let itemsLeft = items.length, attachmentsLeft = attachments.length;
|
||||||
|
while (translatorItem = getter.nextItem()) {
|
||||||
|
assert.isString(translatorItem.itemType, 'itemType is set' + suffix);
|
||||||
|
|
||||||
|
// Standalone attachments
|
||||||
|
if (translatorItem.itemType == 'attachment') {
|
||||||
|
translatorAttachments.push({
|
||||||
|
child: false,
|
||||||
|
attachment: translatorItem
|
||||||
|
});
|
||||||
|
attachmentsLeft--;
|
||||||
|
|
||||||
|
// Child attachments
|
||||||
|
} else if (translatorItem.itemType == 'journalArticle') {
|
||||||
|
assert.isArray(translatorItem.attachments, 'item contains attachment array' + suffix);
|
||||||
|
assert.equal(translatorItem.attachments.length, 3, 'attachment array contains all items' + suffix);
|
||||||
|
|
||||||
|
for (let i=0; i<translatorItem.attachments.length; i++) {
|
||||||
|
let attachment = translatorItem.attachments[i];
|
||||||
|
assert.equal(attachment.itemType, 'attachment', 'item attachment is of itemType "attachment"' + suffix);
|
||||||
|
|
||||||
|
translatorAttachments.push({
|
||||||
|
child: true,
|
||||||
|
attachment: attachment
|
||||||
|
});
|
||||||
|
|
||||||
|
attachmentsLeft--;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unexpected
|
||||||
|
} else {
|
||||||
|
assert.fail(translatorItem.itemType, 'attachment or journalArticle', 'expected itemType returned');
|
||||||
|
}
|
||||||
|
|
||||||
|
itemsLeft--;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.equal(itemsLeft, 0, 'all items returned by getter');
|
||||||
|
assert.equal(attachmentsLeft, 0, 'all attachments returned by getter');
|
||||||
|
|
||||||
|
// Since we make no guarantees on the order of child attachments,
|
||||||
|
// we have to rely on URI as the identifier
|
||||||
|
let uriMap = {};
|
||||||
|
for (let i=0; i<attachments.length; i++) {
|
||||||
|
uriMap[Zotero.URI.getItemURI(attachments[i])] = attachments[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let j=0; j<translatorAttachments.length; j++) {
|
||||||
|
let childAttachment = translatorAttachments[j].child;
|
||||||
|
let attachment = translatorAttachments[j].attachment;
|
||||||
|
assert.isString(attachment.uri, 'uri is set' + suffix);
|
||||||
|
|
||||||
|
let zoteroItem = uriMap[attachment.uri];
|
||||||
|
assert.isDefined(zoteroItem, 'uri is correct' + suffix);
|
||||||
|
delete uriMap[attachment.uri];
|
||||||
|
|
||||||
|
let storedFile = zoteroItem.attachmentLinkMode == Zotero.Attachments.LINK_MODE_IMPORTED_FILE
|
||||||
|
|| zoteroItem.attachmentLinkMode == Zotero.Attachments.LINK_MODE_IMPORTED_URL;
|
||||||
|
let linkToURL = zoteroItem.attachmentLinkMode == Zotero.Attachments.LINK_MODE_LINKED_URL;
|
||||||
|
|
||||||
|
let prefix = (childAttachment ? 'attached ' : '')
|
||||||
|
+ (storedFile ? 'stored ' : 'link to ')
|
||||||
|
+ (linkToURL ? 'URL ' : 'file ');
|
||||||
|
|
||||||
|
// Set fields
|
||||||
|
assert.equal(attachment.itemType, 'attachment', prefix + 'itemType is correct' + suffix);
|
||||||
|
assert.equal(attachment.title, 'empty.pdf', prefix + 'title is correct' + suffix);
|
||||||
|
assert.equal(attachment.url, 'http://example.com', prefix + 'url is correct' + suffix);
|
||||||
|
assert.equal(attachment.charset, 'utf-8', prefix + 'charset is correct' + suffix);
|
||||||
|
assert.equal(attachment.note, 'note', prefix + 'note is correct' + suffix);
|
||||||
|
|
||||||
|
// Automatically set fields
|
||||||
|
assert.isString(attachment.dateAdded, prefix + 'dateAdded is set' + suffix);
|
||||||
|
assert.isString(attachment.dateModified, prefix + 'dateModified is set' + suffix);
|
||||||
|
|
||||||
|
// Legacy mode fields
|
||||||
|
if (legacy) {
|
||||||
|
assert.isNumber(attachment.itemID, prefix + 'itemID is set' + suffix);
|
||||||
|
assert.isString(attachment.key, prefix + 'key is set' + suffix);
|
||||||
|
assert.equal(attachment.mimeType, 'application/pdf', prefix + 'mimeType is correct' + suffix);
|
||||||
|
|
||||||
|
assert.equal(attachment.accessDate, '2001-02-03 12:13:14', prefix + 'accessDate is correct' + suffix);
|
||||||
|
|
||||||
|
assert.isTrue(sqlDateTimeRe.test(attachment.dateAdded), prefix + 'dateAdded matches SQL format' + suffix);
|
||||||
|
assert.isTrue(sqlDateTimeRe.test(attachment.dateModified), prefix + 'dateModified matches SQL format' + suffix);
|
||||||
|
} else {
|
||||||
|
assert.equal(attachment.contentType, 'application/pdf', prefix + 'contentType is correct' + suffix);
|
||||||
|
|
||||||
|
assert.equal(attachment.accessDate, '2001-02-03T12:13:14Z', prefix + 'accessDate is correct' + suffix);
|
||||||
|
|
||||||
|
assert.isTrue(isoDateTimeRe.test(attachment.dateAdded), prefix + 'dateAdded matches ISO-8601 format' + suffix);
|
||||||
|
assert.isTrue(isoDateTimeRe.test(attachment.dateModified), prefix + 'dateModified matches ISO-8601 format' + suffix);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!linkToURL) {
|
||||||
|
// localPath
|
||||||
|
assert.isString(attachment.localPath, prefix + 'localPath is set' + suffix);
|
||||||
|
let attachmentFile = Components.classes["@mozilla.org/file/local;1"]
|
||||||
|
.createInstance(Components.interfaces.nsILocalFile);
|
||||||
|
attachmentFile.initWithPath(attachment.localPath);
|
||||||
|
assert.isTrue(attachmentFile.exists(), prefix + 'localPath points to a file' + suffix);
|
||||||
|
assert.isTrue(attachmentFile.equals(attachments[j].getFile(null, true)), prefix + 'localPath points to the correct file' + suffix);
|
||||||
|
|
||||||
|
assert.equal(attachment.filename, 'empty.pdf', prefix + 'filename is correct' + suffix);
|
||||||
|
assert.equal(attachment.defaultPath, 'files/' + attachments[j].id + '/' + attachment.filename, prefix + 'defaultPath is correct' + suffix);
|
||||||
|
|
||||||
|
// saveFile function
|
||||||
|
assert.isFunction(attachment.saveFile, prefix + 'has saveFile function' + suffix);
|
||||||
|
attachment.saveFile(attachment.defaultPath);
|
||||||
|
assert.equal(attachment.path, OS.Path.join(exportDir, OS.Path.normalize(attachment.defaultPath)), prefix + 'path is set correctly after saveFile call' + suffix);
|
||||||
|
|
||||||
|
let fileExists = yield OS.File.exists(attachment.path);
|
||||||
|
assert.isTrue(fileExists, prefix + 'file was copied to the correct path by saveFile function' + suffix);
|
||||||
|
fileExists = yield OS.File.exists(attachment.localPath);
|
||||||
|
assert.isTrue(fileExists, prefix + 'file was not removed from original location' + suffix);
|
||||||
|
|
||||||
|
assert.throws(attachment.saveFile.bind(attachment, attachment.defaultPath), /^ERROR_FILE_EXISTS /, prefix + 'saveFile does not overwrite existing file by default' + suffix);
|
||||||
|
assert.throws(attachment.saveFile.bind(attachment, 'file/../../'), /./, prefix + 'saveFile does not allow exporting outside export directory' + suffix);
|
||||||
|
/** TODO: check if overwriting existing file works **/
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tags
|
||||||
|
assert.isArray(attachment.tags, prefix + 'contains tags as array' + suffix);
|
||||||
|
assert.equal(attachment.tags.length, 2, prefix + 'contains correct number of tags' + suffix);
|
||||||
|
let possibleTags = [
|
||||||
|
{ tag: 'automaticTag', type: 0 },
|
||||||
|
{ tag: 'manualTag', type: 1 }
|
||||||
|
];
|
||||||
|
for (let i=0; i<possibleTags.length; i++) {
|
||||||
|
let match = false;
|
||||||
|
for (let j=0; j<attachment.tags.length; j++) {
|
||||||
|
if (possibleTags[i].tag == attachment.tags[j].tag) {
|
||||||
|
let type = possibleTags[i].type;
|
||||||
|
if (!legacy && type == 0) type = undefined;
|
||||||
|
|
||||||
|
assert.equal(attachment.tags[j].type, type, prefix + possibleTags[i].tag + ' tag is correct' + suffix);
|
||||||
|
match = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert.isTrue(match, prefix + ' has ' + possibleTags[i].tag + ' tag ' + suffix);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Relations
|
||||||
|
assert.isObject(attachment.relations, prefix + 'has relations as object' + suffix);
|
||||||
|
assert.equal(attachment.relations['dc:relation'], Zotero.URI.getItemURI(relatedItem), prefix + 'relation is correct' + suffix);
|
||||||
|
/** TODO: test other relations and multiple relations per predicate (should be an array) **/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
});
|
|
@ -172,4 +172,105 @@ describe("Zotero.Utilities", function() {
|
||||||
assert.equal(cleanISSN('<b>ISSN</b>:1234\xA0-\t5679(print)\n<b>eISSN (electronic)</b>:0028-0836'), '1234-5679');
|
assert.equal(cleanISSN('<b>ISSN</b>:1234\xA0-\t5679(print)\n<b>eISSN (electronic)</b>:0028-0836'), '1234-5679');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
describe("itemToCSLJSON", function() {
|
||||||
|
it("should accept Zotero.Item and Zotero export item format", function() {
|
||||||
|
let data = populateDBWithSampleData(loadSampleData('journalArticle'));
|
||||||
|
let item = Zotero.Items.get(data.journalArticle.id);
|
||||||
|
|
||||||
|
let fromZoteroItem;
|
||||||
|
try {
|
||||||
|
fromZoteroItem = Zotero.Utilities.itemToCSLJSON(item);
|
||||||
|
} catch(e) {
|
||||||
|
assert.fail(e, null, 'accepts Zotero Item');
|
||||||
|
}
|
||||||
|
assert.isObject(fromZoteroItem, 'converts Zotero Item to object');
|
||||||
|
assert.isNotNull(fromZoteroItem, 'converts Zotero Item to non-null object');
|
||||||
|
|
||||||
|
|
||||||
|
let fromExportItem;
|
||||||
|
try {
|
||||||
|
fromExportItem = Zotero.Utilities.itemToCSLJSON(
|
||||||
|
Zotero.Utilities.Internal.itemToExportFormat(item)
|
||||||
|
);
|
||||||
|
} catch(e) {
|
||||||
|
assert.fail(e, null, 'accepts Zotero export item');
|
||||||
|
}
|
||||||
|
assert.isObject(fromExportItem, 'converts Zotero export item to object');
|
||||||
|
assert.isNotNull(fromExportItem, 'converts Zotero export item to non-null object');
|
||||||
|
|
||||||
|
assert.deepEqual(fromZoteroItem, fromExportItem, 'conversion from Zotero Item and from export item are the same');
|
||||||
|
});
|
||||||
|
it("should convert standalone notes to expected format", function() {
|
||||||
|
let note = new Zotero.Item('note');
|
||||||
|
note.setNote('Some note longer than 50 characters, which will become the title.');
|
||||||
|
note = Zotero.Items.get(note.save());
|
||||||
|
|
||||||
|
let cslJSONNote = Zotero.Utilities.itemToCSLJSON(note);
|
||||||
|
assert.equal(cslJSONNote.type, 'article', 'note is exported as "article"');
|
||||||
|
assert.equal(cslJSONNote.title, note.getNoteTitle(), 'note title is set to Zotero pseudo-title');
|
||||||
|
});
|
||||||
|
it("should convert standalone attachments to expected format", function() {
|
||||||
|
let file = getTestDataDirectory();
|
||||||
|
file.append("empty.pdf");
|
||||||
|
|
||||||
|
let attachment = Zotero.Items.get(Zotero.Attachments.importFromFile(file));
|
||||||
|
attachment.setField('title', 'Empty');
|
||||||
|
attachment.setField('accessDate', '2001-02-03 12:13:14');
|
||||||
|
attachment.setField('url', 'http://example.com');
|
||||||
|
attachment.setNote('Note');
|
||||||
|
|
||||||
|
attachment.save();
|
||||||
|
|
||||||
|
cslJSONAttachment = Zotero.Utilities.itemToCSLJSON(attachment);
|
||||||
|
assert.equal(cslJSONAttachment.type, 'article', 'attachment is exported as "article"');
|
||||||
|
assert.equal(cslJSONAttachment.title, 'Empty', 'attachment title is correct');
|
||||||
|
assert.deepEqual(cslJSONAttachment.accessed, {"date-parts":[["2001",2,3]]}, 'attachment access date is mapped correctly');
|
||||||
|
});
|
||||||
|
it("should refuse to convert unexpected item types", function() {
|
||||||
|
let data = populateDBWithSampleData(loadSampleData('journalArticle'));
|
||||||
|
let item = Zotero.Items.get(data.journalArticle.id);
|
||||||
|
|
||||||
|
let exportFormat = Zotero.Utilities.Internal.itemToExportFormat(item);
|
||||||
|
exportFormat.itemType = 'foo';
|
||||||
|
|
||||||
|
assert.throws(Zotero.Utilities.itemToCSLJSON.bind(Zotero.Utilities, exportFormat), /^Unexpected Zotero Item type ".*"$/, 'throws an error when trying to map invalid item types');
|
||||||
|
});
|
||||||
|
it("should map additional fields from Extra field", function() {
|
||||||
|
let item = new Zotero.Item('journalArticle');
|
||||||
|
item.setField('extra', 'PMID: 12345\nPMCID:123456');
|
||||||
|
item = Zotero.Items.get(item.save());
|
||||||
|
|
||||||
|
let cslJSON = Zotero.Utilities.itemToCSLJSON(item);
|
||||||
|
|
||||||
|
assert.equal(cslJSON.PMID, '12345', 'PMID from Extra is mapped to PMID');
|
||||||
|
assert.equal(cslJSON.PMCID, '123456', 'PMCID from Extra is mapped to PMCID');
|
||||||
|
|
||||||
|
item.setField('extra', 'PMID: 12345');
|
||||||
|
item.save();
|
||||||
|
cslJSON = Zotero.Utilities.itemToCSLJSON(item);
|
||||||
|
|
||||||
|
assert.equal(cslJSON.PMID, '12345', 'single-line entry is extracted correctly');
|
||||||
|
|
||||||
|
item.setField('extra', 'some junk: note\nPMID: 12345\nstuff in-between\nPMCID: 123456\nlast bit of junk!');
|
||||||
|
item.save();
|
||||||
|
cslJSON = Zotero.Utilities.itemToCSLJSON(item);
|
||||||
|
|
||||||
|
assert.equal(cslJSON.PMID, '12345', 'PMID from mixed Extra field is mapped to PMID');
|
||||||
|
assert.equal(cslJSON.PMCID, '123456', 'PMCID from mixed Extra field is mapped to PMCID');
|
||||||
|
|
||||||
|
item.setField('extra', 'a\n PMID: 12345\nfoo PMCID: 123456');
|
||||||
|
item.save();
|
||||||
|
cslJSON = Zotero.Utilities.itemToCSLJSON(item);
|
||||||
|
|
||||||
|
assert.isUndefined(cslJSON.PMCID, 'field label must not be preceded by other text');
|
||||||
|
assert.isUndefined(cslJSON.PMID, 'field label must not be preceded by a space');
|
||||||
|
assert.equal(cslJSON.note, 'a\n PMID: 12345\nfoo PMCID: 123456', 'note is left untouched if nothing is extracted');
|
||||||
|
|
||||||
|
item.setField('extra', 'something\npmid: 12345\n');
|
||||||
|
item.save();
|
||||||
|
cslJSON = Zotero.Utilities.itemToCSLJSON(item);
|
||||||
|
|
||||||
|
assert.isUndefined(cslJSON.PMID, 'field labels are case-sensitive');
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Add table
Reference in a new issue