511 lines
14 KiB
JavaScript
511 lines
14 KiB
JavaScript
/*
|
|
***** BEGIN LICENSE BLOCK *****
|
|
|
|
Copyright © 2009 Center for History and New Media
|
|
George Mason University, Fairfax, Virginia, USA
|
|
http://zotero.org
|
|
|
|
This file is part of Zotero.
|
|
|
|
Zotero is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU Affero General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
Zotero is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU Affero General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with Zotero. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
***** END LICENSE BLOCK *****
|
|
*/
|
|
|
|
|
|
Zotero.ItemFields = new function() {
|
|
// Private members
|
|
var _fields = {};
|
|
var _fieldsFormats = [];
|
|
var _fieldsLoaded;
|
|
var _itemTypeFieldsLoaded;
|
|
var _fieldFormats = [];
|
|
var _itemTypeFields = [];
|
|
var _baseTypeFields = [];
|
|
var _baseMappedFields = [];
|
|
var _typeFieldIDsByBase = {};
|
|
var _typeFieldNamesByBase = {};
|
|
var _baseFieldIDsByTypeAndField = {};
|
|
|
|
// Privileged methods
|
|
this.getName = getName;
|
|
this.getID = getID;
|
|
this.getLocalizedString = getLocalizedString;
|
|
this.isValidForType = isValidForType;
|
|
this.isInteger = isInteger;
|
|
this.getItemTypeFields = getItemTypeFields;
|
|
this.isBaseField = isBaseField;
|
|
this.isFieldOfBase = isFieldOfBase;
|
|
this.getFieldIDFromTypeAndBase = getFieldIDFromTypeAndBase;
|
|
this.getBaseIDFromTypeAndField = getBaseIDFromTypeAndField;
|
|
this.getTypeFieldsFromBase = getTypeFieldsFromBase;
|
|
|
|
|
|
/*
|
|
* Load all fields into an internal hash array
|
|
*/
|
|
this.init = Zotero.Promise.coroutine(function* () {
|
|
_fields = {};
|
|
_fieldsFormats = [];
|
|
|
|
var result = yield Zotero.DB.queryAsync('SELECT * FROM fieldFormats');
|
|
|
|
for (var i=0; i<result.length; i++) {
|
|
_fieldFormats[result[i]['fieldFormatID']] = {
|
|
regex: result[i]['regex'],
|
|
isInteger: result[i]['isInteger']
|
|
};
|
|
}
|
|
|
|
var fields = yield Zotero.DB.queryAsync('SELECT * FROM fieldsCombined');
|
|
|
|
var fieldItemTypes = yield _getFieldItemTypes();
|
|
|
|
var sql = "SELECT DISTINCT baseFieldID FROM baseFieldMappingsCombined";
|
|
var baseFields = yield Zotero.DB.columnQueryAsync(sql);
|
|
|
|
for (let field of fields) {
|
|
_fields[field['fieldID']] = {
|
|
id: field['fieldID'],
|
|
name: field.fieldName,
|
|
label: field.label,
|
|
custom: !!field.custom,
|
|
isBaseField: (baseFields.indexOf(field['fieldID']) != -1),
|
|
formatID: field['fieldFormatID'],
|
|
itemTypes: fieldItemTypes[field['fieldID']]
|
|
};
|
|
// Store by name as well as id
|
|
_fields[field['fieldName']] = _fields[field['fieldID']];
|
|
}
|
|
|
|
_fieldsLoaded = true;
|
|
yield _loadBaseTypeFields();
|
|
yield _loadItemTypeFields();
|
|
});
|
|
|
|
|
|
/*
|
|
* Return the fieldID for a passed fieldID or fieldName
|
|
*/
|
|
function getID(field) {
|
|
if (!_fieldsLoaded) {
|
|
throw new Zotero.Exception.UnloadedDataException("Item field data not yet loaded");
|
|
}
|
|
|
|
if (typeof field == 'number') {
|
|
return _fields[field] ? field : false;
|
|
}
|
|
|
|
return _fields[field] ? _fields[field]['id'] : false;
|
|
}
|
|
|
|
|
|
/*
|
|
* Return the fieldName for a passed fieldID or fieldName
|
|
*/
|
|
function getName(field) {
|
|
if (!_fieldsLoaded) {
|
|
throw new Zotero.Exception.UnloadedDataException("Item field data not yet loaded");
|
|
}
|
|
|
|
return _fields[field] ? _fields[field]['name'] : false;
|
|
}
|
|
|
|
|
|
function getLocalizedString(itemType, field) {
|
|
// unused currently
|
|
//var typeName = Zotero.ItemTypes.getName(itemType);
|
|
var fieldName = Zotero.ItemFields.getName(field);
|
|
|
|
// Fields in the items table are special cases
|
|
switch (field) {
|
|
case 'dateAdded':
|
|
case 'dateModified':
|
|
case 'itemType':
|
|
return Zotero.getString("itemFields." + field);
|
|
}
|
|
|
|
// TODO: different labels for different item types
|
|
|
|
_fieldCheck(field, 'getLocalizedString');
|
|
|
|
if (_fields[field].label) {
|
|
return _fields[field].label;
|
|
}
|
|
else {
|
|
try {
|
|
var loc = Zotero.getString("itemFields." + fieldName);
|
|
}
|
|
// If localized string not found, try base field
|
|
catch (e) {
|
|
Zotero.debug("Localized string not found for field '" + fieldName + "' -- trying base field");
|
|
var baseFieldID = this.getBaseIDFromTypeAndField(itemType, field);
|
|
fieldName = this.getName(baseFieldID);
|
|
var loc = Zotero.getString("itemFields." + fieldName);
|
|
}
|
|
return loc;
|
|
}
|
|
}
|
|
|
|
|
|
function isValidForType(fieldID, itemTypeID) {
|
|
fieldID = getID(fieldID);
|
|
if (!fieldID) return false;
|
|
|
|
if (!_fields[fieldID]['itemTypes']) {
|
|
return false;
|
|
}
|
|
|
|
return !!_fields[fieldID]['itemTypes'][itemTypeID];
|
|
}
|
|
|
|
|
|
function isInteger(fieldID) {
|
|
_fieldCheck(fieldID, 'isInteger');
|
|
|
|
var ffid = _fields[fieldID]['formatID'];
|
|
return _fieldFormats[ffid] ? _fieldFormats[ffid]['isInteger'] : false;
|
|
}
|
|
|
|
|
|
this.isCustom = function (fieldID) {
|
|
_fieldCheck(fieldID, 'isCustom');
|
|
|
|
return _fields[fieldID].custom;
|
|
}
|
|
|
|
|
|
/*
|
|
* Returns an array of fieldIDs for a given item type
|
|
*/
|
|
function getItemTypeFields(itemTypeID) {
|
|
if (!itemTypeID) {
|
|
let e = new Error("Invalid item type id '" + itemTypeID + "'");
|
|
e.name = "ZoteroUnknownTypeError";
|
|
throw e;
|
|
}
|
|
|
|
if (!_itemTypeFieldsLoaded) {
|
|
throw new Zotero.Exception.UnloadedDataException("Item field data not yet loaded");
|
|
}
|
|
|
|
if (!_itemTypeFields[itemTypeID]) {
|
|
throw new Error("Item type field data not found for itemTypeID " + itemTypeID);
|
|
}
|
|
|
|
return [..._itemTypeFields[itemTypeID]];
|
|
}
|
|
|
|
|
|
function isBaseField(field) {
|
|
_fieldCheck(field, arguments.callee.name);
|
|
|
|
return _fields[field]['isBaseField'];
|
|
}
|
|
|
|
|
|
function isFieldOfBase(field, baseField) {
|
|
var fieldID = _fieldCheck(field, 'isFieldOfBase');
|
|
|
|
var baseFieldID = this.getID(baseField);
|
|
if (!baseFieldID) {
|
|
throw new Error("Invalid field '" + baseField + '" for base field');
|
|
}
|
|
|
|
if (fieldID == baseFieldID) {
|
|
return true;
|
|
}
|
|
|
|
var typeFields = this.getTypeFieldsFromBase(baseFieldID);
|
|
return typeFields.indexOf(fieldID) != -1;
|
|
}
|
|
|
|
|
|
this.getBaseMappedFields = function () {
|
|
return _baseMappedFields.concat();
|
|
}
|
|
|
|
|
|
/*
|
|
* Returns the fieldID of a type-specific field for a given base field
|
|
* or false if none
|
|
*
|
|
* Examples:
|
|
*
|
|
* 'audioRecording' and 'publisher' returns label's fieldID
|
|
* 'book' and 'publisher' returns publisher's fieldID
|
|
* 'audioRecording' and 'number' returns false
|
|
*
|
|
* Accepts names or ids
|
|
*/
|
|
function getFieldIDFromTypeAndBase(itemType, baseField) {
|
|
var itemTypeID = Zotero.ItemTypes.getID(itemType);
|
|
if (!itemTypeID) {
|
|
throw new Error("Invalid item type '" + itemType + "'");
|
|
}
|
|
|
|
var baseFieldID = this.getID(baseField);
|
|
if (!baseFieldID) {
|
|
throw new Error("Invalid field '" + baseField + '" for base field');
|
|
}
|
|
|
|
return _baseTypeFields[itemTypeID][baseFieldID];
|
|
}
|
|
|
|
|
|
/*
|
|
* Returns the fieldID of the base field for a given type-specific field
|
|
* or false if none
|
|
*
|
|
* Examples:
|
|
*
|
|
* 'audioRecording' and 'label' returns publisher's fieldID
|
|
* 'book' and 'publisher' returns publisher's fieldID
|
|
* 'audioRecording' and 'runningTime' returns false
|
|
*
|
|
* Accepts names or ids
|
|
*/
|
|
function getBaseIDFromTypeAndField(itemType, typeField) {
|
|
var itemTypeID = Zotero.ItemTypes.getID(itemType);
|
|
var typeFieldID = this.getID(typeField);
|
|
|
|
if (!itemTypeID) {
|
|
throw new Error("Invalid item type '" + itemType + "'");
|
|
}
|
|
|
|
_fieldCheck(typeField, 'getBaseIDFromTypeAndField');
|
|
|
|
if (!this.isValidForType(typeFieldID, itemTypeID)) {
|
|
throw new Error("'" + typeField + "' is not a valid field for '" + itemType + "'");
|
|
}
|
|
|
|
// If typeField is already a base field, just return that
|
|
if (this.isBaseField(typeFieldID)) {
|
|
return typeFieldID;
|
|
}
|
|
|
|
if (!_baseFieldIDsByTypeAndField[itemTypeID]) return false;
|
|
return _baseFieldIDsByTypeAndField[itemTypeID][typeFieldID] || false;
|
|
}
|
|
|
|
|
|
/*
|
|
* Returns an array of fieldIDs associated with a given base field
|
|
*
|
|
* e.g. 'publisher' returns fieldIDs for [university, studio, label, network]
|
|
*/
|
|
function getTypeFieldsFromBase(baseField, asNames) {
|
|
var baseFieldID = this.getID(baseField);
|
|
if (!baseFieldID) {
|
|
throw ("Invalid base field '" + baseField + '" in ItemFields.getTypeFieldsFromBase()');
|
|
}
|
|
|
|
if (asNames) {
|
|
return _typeFieldNamesByBase[baseFieldID] ?
|
|
_typeFieldNamesByBase[baseFieldID] : false;
|
|
}
|
|
|
|
return _typeFieldIDsByBase[baseFieldID] ?
|
|
[..._typeFieldIDsByBase[baseFieldID]] : false;
|
|
}
|
|
|
|
|
|
this.isAutocompleteField = function (field) {
|
|
field = this.getName(field);
|
|
|
|
var autoCompleteFields = [
|
|
'journalAbbreviation',
|
|
'series',
|
|
'seriesTitle',
|
|
'seriesText',
|
|
'libraryCatalog',
|
|
'callNumber',
|
|
'archive',
|
|
'archiveLocation',
|
|
'language',
|
|
'programmingLanguage',
|
|
'rights',
|
|
|
|
// TEMP - NSF
|
|
'programDirector',
|
|
'institution',
|
|
'discipline'
|
|
];
|
|
|
|
// Add the type-specific versions of these base fields
|
|
var baseACFields = ['publisher', 'publicationTitle', 'type', 'medium', 'place'];
|
|
autoCompleteFields = autoCompleteFields.concat(baseACFields);
|
|
|
|
for (var i=0; i<baseACFields.length; i++) {
|
|
var add = Zotero.ItemFields.getTypeFieldsFromBase(baseACFields[i], true)
|
|
autoCompleteFields = autoCompleteFields.concat(add);
|
|
}
|
|
|
|
return autoCompleteFields.indexOf(field) != -1;
|
|
}
|
|
|
|
|
|
/**
|
|
* A long field expands into a multiline textbox while editing but displays
|
|
* as a single line in non-editing mode; newlines are not allowed
|
|
*/
|
|
this.isLong = function (field) {
|
|
field = this.getName(field);
|
|
var fields = [
|
|
'title',
|
|
'bookTitle'
|
|
];
|
|
return fields.indexOf(field) != -1;
|
|
}
|
|
|
|
|
|
/**
|
|
* A multiline field displays as a multiline text box in editing mode
|
|
* and non-editing mode; newlines are allowed
|
|
*/
|
|
this.isMultiline = function (field) {
|
|
field = this.getName(field);
|
|
var fields = [
|
|
'abstractNote',
|
|
'extra',
|
|
|
|
// TEMP - NSF
|
|
'address'
|
|
];
|
|
return fields.indexOf(field) != -1;
|
|
}
|
|
|
|
|
|
/**
|
|
* Check whether a field is valid, throwing an exception if not
|
|
* (since it should never actually happen)
|
|
**/
|
|
function _fieldCheck(field, func) {
|
|
var fieldID = Zotero.ItemFields.getID(field);
|
|
if (!fieldID) {
|
|
Zotero.debug((new Error).stack, 1);
|
|
throw new Error("Invalid field '" + field + (func ? "' in ItemFields." + func + "()" : "'"));
|
|
}
|
|
return fieldID;
|
|
}
|
|
|
|
|
|
/*
|
|
* Returns hash array of itemTypeIDs for which a given field is valid
|
|
*/
|
|
var _getFieldItemTypes = Zotero.Promise.coroutine(function* () {
|
|
var sql = 'SELECT fieldID, itemTypeID FROM itemTypeFieldsCombined';
|
|
var results = yield Zotero.DB.queryAsync(sql);
|
|
|
|
if (!results) {
|
|
throw ('No fields in itemTypeFields!');
|
|
}
|
|
var fields = [];
|
|
for (let i=0; i<results.length; i++) {
|
|
if (!fields[results[i].fieldID]) {
|
|
fields[results[i].fieldID] = [];
|
|
}
|
|
fields[results[i].fieldID][results[i].itemTypeID] = true;
|
|
}
|
|
return fields;
|
|
});
|
|
|
|
|
|
/*
|
|
* Build a lookup table for base field mappings
|
|
*/
|
|
var _loadBaseTypeFields = Zotero.Promise.coroutine(function* () {
|
|
_typeFieldIDsByBase = {};
|
|
_typeFieldNamesByBase = {};
|
|
|
|
// Grab all fields, base field or not
|
|
var sql = "SELECT IT.itemTypeID, F.fieldID AS baseFieldID, BFM.fieldID "
|
|
+ "FROM itemTypesCombined IT LEFT JOIN fieldsCombined F "
|
|
+ "LEFT JOIN baseFieldMappingsCombined BFM"
|
|
+ " ON (IT.itemTypeID=BFM.itemTypeID AND F.fieldID=BFM.baseFieldID)";
|
|
var rows = yield Zotero.DB.queryAsync(sql);
|
|
|
|
var sql = "SELECT DISTINCT baseFieldID FROM baseFieldMappingsCombined";
|
|
var baseFields = yield Zotero.DB.columnQueryAsync(sql);
|
|
|
|
var fields = [];
|
|
for (let row of rows) {
|
|
if (!fields[row.itemTypeID]) {
|
|
fields[row.itemTypeID] = [];
|
|
}
|
|
if (row.fieldID) {
|
|
fields[row.itemTypeID][row.baseFieldID] = row.fieldID;
|
|
}
|
|
// If a base field and already valid for the type, just use that
|
|
else if (isBaseField(row.baseFieldID) &&
|
|
isValidForType(row.baseFieldID, row.itemTypeID)) {
|
|
fields[row.itemTypeID][row.baseFieldID] = row.baseFieldID;
|
|
}
|
|
// Set false for other fields so that we don't need to test for
|
|
// existence
|
|
else {
|
|
fields[row.itemTypeID][row.baseFieldID] = false;
|
|
}
|
|
}
|
|
_baseTypeFields = fields;
|
|
|
|
var sql = "SELECT itemTypeID, baseFieldID, fieldID, fieldName "
|
|
+ "FROM baseFieldMappingsCombined JOIN fieldsCombined USING (fieldID)";
|
|
var rows = yield Zotero.DB.queryAsync(sql);
|
|
for (let i = 0; i < rows.length; i++) {
|
|
let row = rows[i];
|
|
// Type fields by base
|
|
if (!_typeFieldIDsByBase[row['baseFieldID']]) {
|
|
_typeFieldIDsByBase[row['baseFieldID']] = [];
|
|
_typeFieldNamesByBase[row['baseFieldID']] = [];
|
|
}
|
|
_typeFieldIDsByBase[row['baseFieldID']].push(row['fieldID']);
|
|
_typeFieldNamesByBase[row['baseFieldID']].push(row['fieldName']);
|
|
|
|
// Base fields by type and field
|
|
if (!_baseFieldIDsByTypeAndField[row.itemTypeID]) {
|
|
_baseFieldIDsByTypeAndField[row.itemTypeID] = {};
|
|
}
|
|
_baseFieldIDsByTypeAndField[row.itemTypeID][row.fieldID] = row.baseFieldID;
|
|
|
|
}
|
|
|
|
// Get all fields mapped to base types
|
|
sql = "SELECT DISTINCT fieldID FROM baseFieldMappingsCombined";
|
|
_baseMappedFields = yield Zotero.DB.columnQueryAsync(sql);
|
|
|
|
_baseTypeFieldsLoaded = true;
|
|
});
|
|
|
|
|
|
var _loadItemTypeFields = Zotero.Promise.coroutine(function* () {
|
|
var sql = 'SELECT itemTypeID, fieldID FROM itemTypeFieldsCombined ORDER BY orderIndex';
|
|
var rows = yield Zotero.DB.queryAsync(sql);
|
|
|
|
_itemTypeFields = {};
|
|
_itemTypeFields[1] = []; // notes have no fields
|
|
|
|
for (let i=0; i<rows.length; i++) {
|
|
let row = rows[i];
|
|
let itemTypeID = row.itemTypeID;
|
|
if (!_itemTypeFields[itemTypeID]) {
|
|
_itemTypeFields[itemTypeID] = [];
|
|
}
|
|
_itemTypeFields[itemTypeID].push(row.fieldID);
|
|
}
|
|
|
|
_itemTypeFieldsLoaded = true;
|
|
});
|
|
}
|