www

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | Submodules | README | LICENSE

cachedTypes.js (20644B)


      1 /*
      2     ***** BEGIN LICENSE BLOCK *****
      3     
      4     Copyright © 2009 Center for History and New Media
      5                      George Mason University, Fairfax, Virginia, USA
      6                      http://zotero.org
      7     
      8     This file is part of Zotero.
      9     
     10     Zotero is free software: you can redistribute it and/or modify
     11     it under the terms of the GNU Affero General Public License as published by
     12     the Free Software Foundation, either version 3 of the License, or
     13     (at your option) any later version.
     14     
     15     Zotero is distributed in the hope that it will be useful,
     16     but WITHOUT ANY WARRANTY; without even the implied warranty of
     17     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     18     GNU Affero General Public License for more details.
     19     
     20     You should have received a copy of the GNU Affero General Public License
     21     along with Zotero.  If not, see <http://www.gnu.org/licenses/>.
     22     
     23     ***** END LICENSE BLOCK *****
     24 */
     25 
     26 
     27 /*
     28  * Base function for retrieving ids and names of static types stored in the DB
     29  * (e.g. creatorType, fileType, charset, itemType)
     30  *
     31  * Extend using the following code within a child constructor:
     32  *
     33  * 	Zotero.CachedTypes.apply(this, arguments);
     34  *  this.constructor.prototype = new Zotero.CachedTypes();
     35  *
     36  * And the following properties:
     37  *
     38  *	this._typeDesc = '';
     39  *	this._typeDescPlural = '';
     40  *	this._idCol = '';
     41  *	this._nameCol = '';
     42  *	this._table = '';
     43  *
     44  * Optional properties:
     45  *
     46  *  this._allowAdd: Allow new types to be added via .add(name)
     47  *	this._ignoreCase: Ignore case when looking for types, and add new types as lowercase
     48  *
     49  * And add .init() to zotero.js
     50  */
     51 Zotero.CachedTypes = function() {
     52 	this._types = null;
     53 	this._typesArray = null;
     54 	var self = this;
     55 	
     56 	// Override these variables in child classes
     57 	this._typeDesc = '';
     58 	this._idCol = '';
     59 	this._nameCol = '';
     60 	this._table = '';
     61 	this._allowAdd = false;
     62 	this._ignoreCase = false;
     63 	this._hasCustom = false;
     64 	
     65 	
     66 	this.init = Zotero.Promise.coroutine(function* () {
     67 		this._types = {};
     68 		this._typesArray = [];
     69 		
     70 		var types = yield this._getTypesFromDB();
     71 		for (let i=0; i<types.length; i++) {
     72 			this._cacheTypeData(types[i]);
     73 		}
     74 	});
     75 	
     76 	
     77 	this.getName = function (idOrName) {
     78 		if (!this._types) {
     79 			throw new Zotero.Exception.UnloadedDataException(
     80 				Zotero.Utilities.capitalize(this._typeDesc) + " data not yet loaded"
     81 			);
     82 		}
     83 		
     84 		if (this._ignoreCase) {
     85 			idOrName = idOrName + '';
     86 			idOrName = idOrName.toLowerCase();
     87 		}
     88 		
     89 		if (!this._types['_' + idOrName]) {
     90 			Zotero.debug(`Unknown ${this._typeDesc} '${idOrName}'`, 1);
     91 			return '';
     92 		}
     93 		
     94 		return this._types['_' + idOrName]['name'];
     95 	}
     96 	
     97 	
     98 	this.getID = function (idOrName) {
     99 		if (!this._types) {
    100 			throw new Zotero.Exception.UnloadedDataException(
    101 				Zotero.Utilities.capitalize(this._typeDesc) + " data not yet loaded"
    102 			);
    103 		}
    104 		
    105 		if (this._ignoreCase) {
    106 			idOrName = idOrName + '';
    107 			idOrName = idOrName.toLowerCase();
    108 		}
    109 		
    110 		if (!this._types['_' + idOrName]) {
    111 			Zotero.debug(`Unknown ${this._typeDesc} '${idOrName}'`, 1);
    112 			return false;
    113 		}
    114 		
    115 		return this._types['_' + idOrName]['id'];
    116 	}
    117 	
    118 	
    119 	this.getAll = this.getTypes = function () {
    120 		if (!this._typesArray) {
    121 			throw new Zotero.Exception.UnloadedDataException(
    122 				Zotero.Utilities.capitalize(this._typeDesc) + " data not yet loaded"
    123 			);
    124 		}
    125 		return this._typesArray;
    126 	}
    127 	
    128 	
    129 	// Currently used only for item types
    130 	this.isCustom = function (idOrName) {
    131 		return this._types['_' + idOrName] && this._types['_' + idOrName].custom ? this._types['_' + idOrName].custom : false;
    132 	}
    133 	
    134 	
    135 	/**
    136 	 * Add a new type to the data and return its id. If the type already exists, return its id.
    137 	 *
    138 	 * @param {String} name - Type name to add
    139 	 * @return {Integer|False} - The type id (new or existing), or false if invalid type name
    140 	 */
    141 	this.add = Zotero.Promise.coroutine(function* (name) {
    142 		if (!this._allowAdd) {
    143 			throw new Error("New " + this._typeDescPlural + " cannot be added");
    144 		}
    145 		
    146 		if (typeof name != 'string' || name === "") {
    147 			throw new Error("'name' must be a string");
    148 		}
    149 		
    150 		var id = this.getID(name);
    151 		if (id) {
    152 			return id;
    153 		}
    154 		
    155 		if (this._ignoreCase) {
    156 			name = name.toLowerCase();
    157 		}
    158 		
    159 		var allow = this._valueCheck(name);
    160 		if (!allow) {
    161 			return false;
    162 		}
    163 		
    164 		var sql = "INSERT INTO " + this._table + " (" + this._nameCol + ") VALUES (?)";
    165 		yield Zotero.DB.queryAsync(sql, name);
    166 		
    167 		sql = "SELECT " + this._idCol + " FROM " + this._table + " WHERE " + this._nameCol + "=?";
    168 		var id = yield Zotero.DB.valueQueryAsync(sql, name);
    169 		
    170 		this._cacheTypeData({
    171 			id: id,
    172 			name: name
    173 		});
    174 		
    175 		return id;
    176 	});
    177 	
    178 	
    179 	this._valueCheck = function (name) {
    180 		return true;
    181 	}
    182 	
    183 	
    184 	/**
    185 	 * @return {Promise}
    186 	 */
    187 	this._getTypesFromDB = function (where, params) {
    188 		return Zotero.DB.queryAsync(
    189 			'SELECT ' + this._idCol + ' AS id, '
    190 				+ this._nameCol + ' AS name'
    191 				+ (this._hasCustom ? ', custom' : '')
    192 				+ ' FROM ' + this._table
    193 				+ (where ? ' ' + where : ''),
    194 			params ? params : false
    195 		);
    196 	};
    197 	
    198 	
    199 	this._cacheTypeData = function (type) {
    200 		// Store as both id and name for access by either
    201 		var typeData = {
    202 			id: type.id,
    203 			name: type.name,
    204 			custom: this._hasCustom ? !!type.custom : false
    205 		}
    206 		this._types['_' + type.id] = typeData;
    207 		if (this._ignoreCase) {
    208 			this._types['_' + type.name.toLowerCase()] = this._types['_' + type.id];
    209 		}
    210 		else {
    211 			this._types['_' + type.name] = this._types['_' + type.id];
    212 		}
    213 		this._typesArray.push(typeData);
    214 	}
    215 }
    216 
    217 
    218 Zotero.CreatorTypes = new function() {
    219 	Zotero.CachedTypes.apply(this, arguments);
    220 	this.constructor.prototype = new Zotero.CachedTypes();
    221 	
    222 	this.isValidForItemType = isValidForItemType;
    223 	
    224 	this._typeDesc = 'creator type';
    225 	this._typeDescPlural = 'creator types';
    226 	this._idCol = 'creatorTypeID';
    227 	this._nameCol = 'creatorType';
    228 	this._table = 'creatorTypes';
    229 	
    230 	var _primaryIDCache;
    231 	var _hasCreatorTypeCache = {};
    232 	var _creatorTypesByItemType = {};
    233 	var _isValidForItemType = {};
    234 	
    235 	
    236 	this.init = Zotero.Promise.coroutine(function* () {
    237 		yield this.constructor.prototype.init.apply(this);
    238 		
    239 		var sql = "SELECT itemTypeID, creatorTypeID AS id, creatorType AS name, primaryField "
    240 			+ "FROM itemTypeCreatorTypes NATURAL JOIN creatorTypes";
    241 		var rows = yield Zotero.DB.queryAsync(sql);
    242 		for (let i=0; i<rows.length; i++) {
    243 			let row = rows[i];
    244 			let itemTypeID = row.itemTypeID;
    245 			if (!_creatorTypesByItemType[itemTypeID]) {
    246 				_creatorTypesByItemType[itemTypeID] = [];
    247 			}
    248 			_creatorTypesByItemType[itemTypeID].push({
    249 				id: row.id,
    250 				name: row.name,
    251 				primaryField: row.primaryField,
    252 				localizedName: this.getLocalizedString(row.name)
    253 			});
    254 		}
    255 		// Sort primary field first, then by localized name
    256 		for (let itemTypeID in _creatorTypesByItemType) {
    257 			_creatorTypesByItemType[itemTypeID].sort((a, b) => {
    258 				if (a.primaryField != b.primaryField) return b.primaryField - a.primaryField;
    259 				return Zotero.localeCompare(a.localizedName, b.localizedName);
    260 			});
    261 			_creatorTypesByItemType[itemTypeID].forEach((x) => {
    262 				delete x.primaryField;
    263 				delete x.localizedName;
    264 			});
    265 		}
    266 		
    267 		// Load primary creator type ids
    268 		_primaryIDCache = {};
    269 		var sql = "SELECT itemTypeID, creatorTypeID FROM itemTypeCreatorTypes "
    270 			+ "WHERE primaryField=1";
    271 		var rows = yield Zotero.DB.queryAsync(sql);
    272 		for (let i=0; i<rows.length; i++) {
    273 			let row = rows[i];
    274 			_primaryIDCache[row.itemTypeID] = row.creatorTypeID;
    275 		}
    276 	});
    277 	
    278 	
    279 	this.getTypesForItemType = function (itemTypeID) {
    280 		if (!_creatorTypesByItemType[itemTypeID]) {
    281 			return [];
    282 		}
    283 		return _creatorTypesByItemType[itemTypeID];
    284 	}
    285 	
    286 	
    287 	function isValidForItemType(creatorTypeID, itemTypeID) {
    288 		if (_isValidForItemType[itemTypeID] && typeof _isValidForItemType[itemTypeID][creatorTypeID] != 'undefined') {
    289 			return _isValidForItemType[itemTypeID][creatorTypeID];
    290 		}
    291 		
    292 		var valid = false;
    293 		var types = this.getTypesForItemType(itemTypeID);
    294 		for (let type of types) {
    295 			if (type.id == creatorTypeID) {
    296 				valid = true;
    297 				break;
    298 			}
    299 		}
    300 		
    301 		if (!_isValidForItemType[itemTypeID]) {
    302 			_isValidForItemType[itemTypeID] = {};
    303 		}
    304 		_isValidForItemType[itemTypeID][creatorTypeID] = valid;
    305 		return valid;
    306 	}
    307 	
    308 	
    309 	this.getLocalizedString = function(idOrName) {
    310 		return Zotero.getString("creatorTypes."+this.getName(idOrName));
    311 	}
    312 	
    313 	
    314 	this.itemTypeHasCreators = function (itemTypeID) {
    315 		if (typeof _hasCreatorTypeCache[itemTypeID] != 'undefined') {
    316 			return _hasCreatorTypeCache[itemTypeID];
    317 		}
    318 		_hasCreatorTypeCache[itemTypeID] = !!this.getTypesForItemType(itemTypeID).length;
    319 		return _hasCreatorTypeCache[itemTypeID];
    320 	}
    321 	
    322 	
    323 	this.getPrimaryIDForType = function (itemTypeID) {
    324 		if (!_primaryIDCache) {
    325 			throw new Zotero.Exception.UnloadedDataException(
    326 				"Primary creator types not yet loaded"
    327 			);
    328 		}
    329 		
    330 		if (_primaryIDCache[itemTypeID] === undefined) {
    331 			return false;
    332 		}
    333 		
    334 		return _primaryIDCache[itemTypeID];
    335 	}
    336 }
    337 
    338 
    339 Zotero.ItemTypes = new function() {
    340 	Zotero.CachedTypes.apply(this, arguments);
    341 	this.constructor.prototype = new Zotero.CachedTypes();
    342 	
    343 	this.customIDOffset = 10000;
    344 	
    345 	this._typeDesc = 'item type';
    346 	this._typeDescPlural = 'item types';
    347 	this._idCol = 'itemTypeID';
    348 	this._nameCol = 'typeName';
    349 	this._table = 'itemTypesCombined';
    350 	this._hasCustom = true;
    351 	
    352 	var _primaryTypes;
    353 	var _secondaryTypes;
    354 	var _hiddenTypes;
    355 	
    356 	var _numPrimary = 5;
    357 	
    358 	var _customImages = {};
    359 	var _customLabels = {};
    360 	
    361 	
    362 	this.init = Zotero.Promise.coroutine(function* () {
    363 		yield this.constructor.prototype.init.apply(this);
    364 		
    365 		// TODO: get rid of ' AND itemTypeID!=5' and just remove display=2
    366 		// from magazineArticle in system.sql
    367 		_primaryTypes = yield this._getTypesFromDB('WHERE (display=2 AND itemTypeID!=5) LIMIT ' + _numPrimary);
    368 		
    369 		// Secondary types
    370 		_secondaryTypes = yield this._getTypesFromDB('WHERE display IN (1,2)');
    371 		
    372 		// Hidden types
    373 		_hiddenTypes = yield this._getTypesFromDB('WHERE display=0')
    374 		
    375 		// Custom labels and icons
    376 		var sql = "SELECT customItemTypeID AS id, label, icon FROM customItemTypes";
    377 		var rows = yield Zotero.DB.queryAsync(sql);
    378 		for (let i=0; i<rows.length; i++) {
    379 			let row = rows[i];
    380 			let id = row.id;
    381 			_customLabels[id] = row.label;
    382 			_customImages[id] = row.icon;
    383 		}
    384 	});
    385 	
    386 	
    387 	this.getPrimaryTypes = function () {
    388 		if (!_primaryTypes) {
    389 			throw new Zotero.Exception.UnloadedDataException("Primary item type data not yet loaded");
    390 		}
    391 		
    392 		var mru = Zotero.Prefs.get('newItemTypeMRU');
    393 		if (mru && mru.length) {
    394 			// Get types from the MRU list
    395 			mru = new Set(
    396 				mru.split(',')
    397 				.slice(0, _numPrimary)
    398 				.map(id => parseInt(id))
    399 				// Ignore 'webpage' item type
    400 				.filter(id => !isNaN(id) && id != 13)
    401 			);
    402 			
    403 			// Add types from defaults until we reach our limit
    404 			for (let i = 0; i < _primaryTypes.length && mru.size < _numPrimary; i++) {
    405 				mru.add(_primaryTypes[i].id);
    406 			}
    407 			
    408 			return Array.from(mru).map(id => ({ id, name: this.getName(id) }));
    409 		}
    410 		
    411 		return _primaryTypes;
    412 	}
    413 
    414 	this.getSecondaryTypes = function () {
    415 		if (!_secondaryTypes) {
    416 			throw new Zotero.Exception.UnloadedDataException("Secondary item type data not yet loaded");
    417 		}
    418 		return _secondaryTypes;
    419 	}
    420 	
    421 	this.getHiddenTypes = function () {
    422 		if (!_hiddenTypes) {
    423 			throw new Zotero.Exception.UnloadedDataException("Hidden item type data not yet loaded");
    424 		}
    425 		return _hiddenTypes;
    426 	}
    427 	
    428 	this.getLocalizedString = function (idOrName) {
    429 		var typeName = this.getName(idOrName);
    430 		
    431 		// For custom types, use provided label
    432 		if (this.isCustom(idOrName)) {
    433 			var id = this.getID(idOrName) - this.customIDOffset;
    434 			if (!_customLabels[id]) {
    435 				throw new Error("Label not available for custom field " + idOrName);
    436 			}
    437 			return _customLabels[id];
    438 		}
    439 		
    440 		return Zotero.getString("itemTypes." + typeName);
    441 	}
    442 	
    443 	this.getImageSrc = function (itemType) {
    444 		var suffix = Zotero.hiDPISuffix;
    445 		
    446 		if (this.isCustom(itemType)) {
    447 			var id = this.getID(itemType) - this.customIDOffset;
    448 			if (!_customImages[id]) {
    449 				throw new Error("Image not available for custom field " + itemType);
    450 			}
    451 			return _customImages[id];
    452 		}
    453 		
    454 		switch (itemType) {
    455 			// Use treeitem.png
    456 			case 'attachment-file':
    457 			case 'document':
    458 				break;
    459 			
    460 			// HiDPI images available
    461 			case 'attachment-link':
    462 			case 'attachment-pdf':
    463 			case 'attachment-web-link':
    464 			case 'artwork':
    465 			case 'audioRecording':
    466 			case 'bill':
    467 			case 'blogPost':
    468 			case 'book':
    469 			case 'bookSection':
    470 			case 'case':
    471 			case 'computerProgram':
    472 			case 'dictionaryEntry':
    473 			case 'email':
    474 			case 'encyclopediaArticle':
    475 			case 'film':
    476 			case 'forumPost':
    477 			case 'hearing':
    478 			case 'instantMessage':
    479 			case 'interview':
    480 			case 'journalArticle':
    481 			case 'letter':
    482 			case 'magazineArticle':
    483 			case 'manuscript':
    484 			case 'newspaperArticle':
    485 			case 'note':
    486 			case 'patent':
    487 			case 'presentation':
    488 			case 'report':
    489 			case 'statute':
    490 			case 'thesis':
    491 			case 'webpage':
    492 				return "chrome://zotero/skin/treeitem-" + itemType + suffix + ".png";
    493 			
    494 			// No HiDPI images available
    495 			case 'attachment-snapshot':
    496 			case 'conferencePaper':
    497 			case 'map':
    498 			case 'podcast':
    499 			case 'radioBroadcast':
    500 			case 'tvBroadcast':
    501 			case 'videoRecording':
    502 				return "chrome://zotero/skin/treeitem-" + itemType + ".png";
    503 		}
    504 		
    505 		return "chrome://zotero/skin/treeitem" + suffix + ".png";
    506 	}
    507 }
    508 
    509 
    510 Zotero.FileTypes = new function() {
    511 	Zotero.CachedTypes.apply(this, arguments);
    512 	this.constructor.prototype = new Zotero.CachedTypes();
    513 	
    514 	this._typeDesc = 'file type';
    515 	this._typeDescPlural = 'file types';
    516 	this._idCol = 'fileTypeID';
    517 	this._nameCol = 'fileType';
    518 	this._table = 'fileTypes';
    519 	
    520 	/**
    521 	 * @return {Promise<Integer>} fileTypeID
    522 	 */
    523 	this.getIDFromMIMEType = function (mimeType) {
    524 		var sql = "SELECT fileTypeID FROM fileTypeMIMETypes "
    525 			+ "WHERE ? LIKE mimeType || '%'";
    526 		return Zotero.DB.valueQueryAsync(sql, [mimeType]);
    527 	};
    528 }
    529 
    530 
    531 Zotero.CharacterSets = new function() {
    532 	Zotero.CachedTypes.apply(this, arguments);
    533 	this.constructor.prototype = new Zotero.CachedTypes();
    534 	
    535 	this._typeDesc = 'character set';
    536 	this._typeDescPlural = 'character sets';
    537 	this._idCol = 'charsetID';
    538 	this._nameCol = 'charset';
    539 	this._table = 'charsets';
    540 	this._ignoreCase = true;
    541 	
    542 	
    543 	// Converts charset label to charset name
    544 	// https://encoding.spec.whatwg.org/#names-and-labels
    545 	// @param {String} charset
    546 	// @return {String|Boolean} Normalized charset name or FALSE if not recognized
    547 	this.toCanonical = function (charset) {
    548 		let canonical = charsetMap[charset.trim().toLowerCase()];
    549 		if (!canonical) {
    550 			Zotero.debug("Unrecognized charset: " + charset);
    551 			return false;
    552 		}
    553 		return canonical;
    554 	};
    555 	
    556 	// Normalizes charset label to conform to DOM standards
    557 	// https://dom.spec.whatwg.org/#dom-document-characterset
    558 	// @param {String} charset
    559 	// @param {Boolean} mozCompat Whether to return a Mozilla-compatible label
    560 	//   for use in Gecko internal APIs.
    561 	//   https://developer.mozilla.org/en-US/docs/Gecko/Character_sets_supported_by_Gecko
    562 	// @return {String|Boolean} Normalized label or FALSE is not recognized
    563 	this.toLabel = function (charset, mozCompat) {
    564 		charset = this.toCanonical(charset);
    565 		if (!charset) return false;
    566 		
    567 		if (mozCompat && charset == 'gbk') return charset; // See https://developer.mozilla.org/en-US/docs/Gecko/Character_sets_supported_by_Gecko
    568 		
    569 		return compatibilityNames[charset];
    570 	}
    571 	
    572 	// From https://encoding.spec.whatwg.org/#names-and-labels
    573 	// Don't use this, use charsetMap. See below
    574 	let charsetList = {
    575 		"utf-8": ["unicode-1-1-utf-8", "utf-8", "utf8"],
    576 		"ibm866": ["866", "cp866", "csibm866", "ibm866"],
    577 		"iso-8859-2": ["csisolatin2", "iso-8859-2", "iso-ir-101", "iso8859-2", "iso88592", "iso_8859-2", 
    578 			"iso_8859-2:1987","l2", "latin2"],
    579 		"iso-8859-3": ["csisolatin3", "iso-8859-3", "iso-ir-109", "iso8859-3", "iso88593", "iso_8859-3", 
    580 			"iso_8859-3:1988","l3", "latin3"],
    581 		"iso-8859-4": ["csisolatin4", "iso-8859-4", "iso-ir-110", "iso8859-4", "iso88594", "iso_8859-4", 
    582 			"iso_8859-4:1988","l4", "latin4"],
    583 		"iso-8859-5": ["csisolatincyrillic", "cyrillic", "iso-8859-5", "iso-ir-144", "iso8859-5", "iso88595", "iso_8859-5", 
    584 			"iso_8859-5:1988"],
    585 		"iso-8859-6": ["arabic", "asmo-708", "csiso88596e", "csiso88596i", "csisolatinarabic", "ecma-114", "iso-8859-6", "iso-8859-6-e", "iso-8859-6-i", "iso-ir-127", "iso8859-6", "iso88596", "iso_8859-6", 
    586 			"iso_8859-6:1987"],
    587 		"iso-8859-7": ["csisolatingreek", "ecma-118", "elot_928", "greek", "greek8", "iso-8859-7", "iso-ir-126", "iso8859-7", "iso88597", "iso_8859-7", 
    588 			"iso_8859-7:1987","sun_eu_greek"],
    589 		"iso-8859-8": ["csiso88598e", "csisolatinhebrew", "hebrew", "iso-8859-8", "iso-8859-8-e", "iso-ir-138", "iso8859-8", "iso88598", "iso_8859-8", 
    590 			"iso_8859-8:1988","visual"],
    591 		"iso-8859-8-i": ["csiso88598i", "iso-8859-8-i", "logical"],
    592 		"iso-8859-10": ["csisolatin6", "iso-8859-10", "iso-ir-157", "iso8859-10", "iso885910", "l6", "latin6"],
    593 		"iso-8859-13": ["iso-8859-13", "iso8859-13", "iso885913"],
    594 		"iso-8859-14": ["iso-8859-14", "iso8859-14", "iso885914"],
    595 		"iso-8859-15": ["csisolatin9", "iso-8859-15", "iso8859-15", "iso885915", "iso_8859-15", "l9"],
    596 		"iso-8859-16": ["iso-8859-16"],
    597 		"koi8-r": ["cskoi8r", "koi", "koi8", "koi8-r", "koi8_r"],
    598 		"koi8-u": ["koi8-u"],
    599 		"macintosh": ["csmacintosh", "mac", "macintosh", "x-mac-roman"],
    600 		"windows-874": ["dos-874", "iso-8859-11", "iso8859-11", "iso885911", "tis-620", "windows-874"],
    601 		"windows-1250": ["cp1250", "windows-1250", "x-cp1250"],
    602 		"windows-1251": ["cp1251", "windows-1251", "x-cp1251"],
    603 		"windows-1252": [
    604 			"ansi_x3.4-1968","ascii", "cp1252", "cp819", "csisolatin1", "ibm819", "iso-8859-1", "iso-ir-100", "iso8859-1", "iso88591", "iso_8859-1", 
    605 			"iso_8859-1:1987","l1", "latin1", "us-ascii", "windows-1252", "x-cp1252"],
    606 		"windows-1253": ["cp1253", "windows-1253", "x-cp1253"],
    607 		"windows-1254": ["cp1254", "csisolatin5", "iso-8859-9", "iso-ir-148", "iso8859-9", "iso88599", "iso_8859-9", 
    608 			"iso_8859-9:1989","l5", "latin5", "windows-1254", "x-cp1254"],
    609 		"windows-1255": ["cp1255", "windows-1255", "x-cp1255"],
    610 		"windows-1256": ["cp1256", "windows-1256", "x-cp1256"],
    611 		"windows-1257": ["cp1257", "windows-1257", "x-cp1257"],
    612 		"windows-1258": ["cp1258", "windows-1258", "x-cp1258"],
    613 		"x-mac-cyrillic": ["x-mac-cyrillic", "x-mac-ukrainian"],
    614 		"gbk": ["chinese", "csgb2312", "csiso58gb231280", "gb2312", "gb_2312", "gb_2312-80", "gbk", "iso-ir-58", "x-gbk"],
    615 		"gb18030": ["gb18030"],
    616 		"big5": ["big5", "cn-big5", "csbig5", "x-x-big5"],
    617 		"big5-hkscs": ["big5-hkscs"], // see https://bugzilla.mozilla.org/show_bug.cgi?id=912470
    618 		"euc-jp": ["cseucpkdfmtjapanese", "euc-jp", "x-euc-jp"],
    619 		"iso-2022-jp": ["csiso2022jp", "iso-2022-jp"],
    620 		"shift_jis": ["csshiftjis", "ms_kanji", "shift-jis", "shift_jis", "sjis", "windows-31j", "x-sjis"],
    621 		"euc-kr": ["cseuckr", "csksc56011987", "euc-kr", "iso-ir-149", "korean", "ks_c_5601-1987", "ks_c_5601-1989", "ksc5601", "ksc_5601", "windows-949"],
    622 		"replacement": ["csiso2022kr", "hz-gb-2312", "iso-2022-cn", "iso-2022-cn-ext", "iso-2022-kr"],
    623 		"utf-16be": ["utf-16be"],
    624 		"utf-16le": ["utf-16", "utf-16le"],
    625 		"x-user-defined": ["x-user-defined"]
    626 	}
    627 	
    628 	// As per https://dom.spec.whatwg.org/#dom-document-characterset
    629 	let compatibilityNames = {
    630 		"utf-8": "UTF-8",
    631 		"ibm866": "IBM866",
    632 		"iso-8859-2": "ISO-8859-2",
    633 		"iso-8859-3": "ISO-8859-3",
    634 		"iso-8859-4": "ISO-8859-4",
    635 		"iso-8859-5": "ISO-8859-5",
    636 		"iso-8859-6": "ISO-8859-6",
    637 		"iso-8859-7": "ISO-8859-7",
    638 		"iso-8859-8": "ISO-8859-8",
    639 		"iso-8859-8-i": "ISO-8859-8-I",
    640 		"iso-8859-10": "ISO-8859-10",
    641 		"iso-8859-13": "ISO-8859-13",
    642 		"iso-8859-14": "ISO-8859-14",
    643 		"iso-8859-15": "ISO-8859-15",
    644 		"iso-8859-16": "ISO-8859-16",
    645 		"koi8-r": "KOI8-R",
    646 		"koi8-u": "KOI8-U",
    647 		"gbk": "GBK",
    648 		"big5": "Big5",
    649 		"euc-jp": "EUC-JP",
    650 		"iso-2022-jp": "ISO-2022-JP",
    651 		"shift_jis": "Shift_JIS",
    652 		"euc-kr": "EUC-KR",
    653 		"utf-16be": "UTF-16BE",
    654 		"utf-16le": "UTF-16LE"
    655 	};
    656 	
    657 	let charsetMap = {};
    658 	for (let canonical in charsetList) {
    659 		charsetMap[canonical.toLowerCase()] = canonical;
    660 		charsetList[canonical].forEach((c) => charsetMap[c.toLowerCase()] = canonical);
    661 		
    662 		if (!compatibilityNames[canonical]) {
    663 			compatibilityNames[canonical] = canonical;
    664 		}
    665 	}
    666 	
    667 	// Clear charsetList
    668 	charsetList = null;
    669 }
    670 
    671 
    672 Zotero.RelationPredicates = new function () {
    673 	Zotero.CachedTypes.apply(this, arguments);
    674 	this.constructor.prototype = new Zotero.CachedTypes();
    675 	
    676 	this._typeDesc = 'relation predicate';
    677 	this._typeDescPlural = 'relation predicates';
    678 	this._idCol = 'predicateID';
    679 	this._nameCol = 'predicate';
    680 	this._table = 'relationPredicates';
    681 	this._ignoreCase = false;
    682 	this._allowAdd = true;
    683 }