<commit_before>/*
    ***** 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 *****
*/

/**
 * @property {Boolean} cacheTranslatorData Whether translator data should be cached or reloaded
 *	every time a translator is accessed
 * @property {Zotero.CSL} lastCSL
 */
Zotero.Styles = new function() {
	var _initialized = false;
	var _styles, _visibleStyles;
	
	var _renamedStyles = null;
	
	Components.utils.import("resource://gre/modules/Services.jsm");
	Components.utils.import("resource://gre/modules/FileUtils.jsm");
	
	this.xsltProcessor = null;
	this.ns = {
		"csl":"http://purl.org/net/xbiblio/csl"
	};
	
	
	/**
	 * Initializes styles cache, loading metadata for styles into memory
	 */
	this.reinit = Zotero.Promise.coroutine(function* () {
		Zotero.debug("Initializing styles");
		var start = new Date;
		_initialized = true;
		
		// Upgrade style locale prefs for 4.0.27
		var bibliographyLocale = Zotero.Prefs.get("export.bibliographyLocale");
		if (bibliographyLocale) {
			Zotero.Prefs.set("export.lastLocale", bibliographyLocale);
			Zotero.Prefs.set("export.quickCopy.locale", bibliographyLocale);
			Zotero.Prefs.clear("export.bibliographyLocale");
		}
		
		_styles = {};
		_visibleStyles = [];
		this.lastCSL = null;
		
		// main dir
		var dir = Zotero.getStylesDirectory().path;
		var num = yield _readStylesFromDirectory(dir, false);
		
		// hidden dir
		var hiddenDir = OS.Path.join(dir, 'hidden');
		if (yield OS.File.exists(hiddenDir)) {
			num += yield _readStylesFromDirectory(hiddenDir, true);
		}
		
		// Sort visible styles by title
		_visibleStyles.sort(function(a, b) {
			return a.title.localeCompare(b.title);
		})
		// .. and freeze, so they can be returned directly
		_visibleStyles = Object.freeze(_visibleStyles);
		
		Zotero.debug("Cached " + num + " styles in " + (new Date - start) + " ms");
		
		// load available CSL locales
		var localeFile = {};
		var locales = {};
		var primaryDialects = {};
		var localesLocation = "chrome://zotero/content/locale/csl/locales.json";
		localeFile = JSON.parse(yield Zotero.File.getContentsFromURLAsync(localesLocation));
		
		primaryDialects = localeFile["primary-dialects"];
		
		// only keep localized language name
		for (let locale in localeFile["language-names"]) {
			locales[locale] = localeFile["language-names"][locale][0];
		}
		
		this.locales = locales;
		this.primaryDialects = primaryDialects;
		
		// Load renamed styles
		_renamedStyles = {};
		yield Zotero.HTTP.request(
			"GET",
			"resource://zotero/schema/renamed-styles.json",
			{
				responseType: 'json'
			}
		)
		.then(function (xmlhttp) {
			// Map some obsolete styles to current ones
			if (xmlhttp.response) {
				_renamedStyles = xmlhttp.response;
			}
		})
	});
	this.init = Zotero.lazy(this.reinit);
	
	/**
	 * Reads all styles from a given directory and caches their metadata
	 * @private
	 */
	var _readStylesFromDirectory = Zotero.Promise.coroutine(function* (dir, hidden) {
		var numCached = 0;
		
		var iterator = new OS.File.DirectoryIterator(dir);
		try {
			while (true) {
				let entries = yield iterator.nextBatch(10); // TODO: adjust as necessary
				if (!entries.length) break;
				
				for (let i = 0; i < entries.length; i++) {
					let entry = entries[i];
					let path = entry.path;
					let fileName = entry.name;
					if (!fileName || fileName[0] === "."
							|| fileName.substr(-4).toLowerCase() !== ".csl"
							|| entry.isDir) continue;
					
					try {
						let code = yield Zotero.File.getContentsAsync(path);
						var style = new Zotero.Style(code, path);
					}
					catch (e) {
						Components.utils.reportError(e);
						Zotero.debug(e, 1);
						continue;
					}
					if(style.styleID) {
						// same style is already cached
						if (_styles[style.styleID]) {
							Components.utils.reportError('Style with ID ' + style.styleID
								+ ' already loaded from ' + _styles[style.styleID].fileName);
						} else {
							// add to cache
							_styles[style.styleID] = style;
							_styles[style.styleID].hidden = hidden;
							if(!hidden) _visibleStyles.push(style);
						}
					}
					numCached++;
				}
			}
		}
		finally {
			iterator.close();
		}
		return numCached;
	});
	
	/**
	 * Gets a style with a given ID
	 * @param {String} id
	 * @param {Boolean} skipMappings Don't automatically return renamed style
	 */
	this.get = function (id, skipMappings) {
		if (!_initialized) {
			throw new Zotero.Exception.UnloadedDataException("Styles not yet loaded", 'styles');
		}
		
		if(!skipMappings) {
			var prefix = "http://www.zotero.org/styles/";
			var shortName = id.replace(prefix, "");
			if(_renamedStyles.hasOwnProperty(shortName) && _styles[prefix + _renamedStyles[shortName]]) {
				let newID = prefix + _renamedStyles[shortName];
				Zotero.debug("Mapping " + id + " to " + newID);
				return _styles[newID];
			}
		}
		
		return _styles[id] || false;
	}
	
	/**
	 * Gets all visible styles
	 * @return {Zotero.Style[]} - An immutable array of Zotero.Style objects
	 */
	this.getVisible = function () {
		if (!_initialized) {
			throw new Zotero.Exception.UnloadedDataException("Styles not yet loaded", 'styles');
		}
		return _visibleStyles; // Immutable
	}
	
	/**
	 * Gets all styles
	 *
	 * @return {Object} - An object with style IDs for keys and Zotero.Style objects for values
	 */
	this.getAll = function () {
		if (!_initialized) {
			throw new Zotero.Exception.UnloadedDataException("Styles not yet loaded", 'styles');
		}
		return _styles;
	}
	
	/**
	 * Validates a style
	 * @param {String} style The style, as a string
	 * @return {Promise} A promise representing the style file. This promise is rejected
	 *    with the validation error if validation fails, or resolved if it is not.
	 */
	this.validate = function(style) {
		var deferred = Zotero.Promise.defer(),
			worker = new Worker("resource://zotero/csl-validator.js"); 
		worker.onmessage = function(event) {
			if(event.data) {
				deferred.reject(event.data);
			} else {
				deferred.resolve();
			}
		};
		worker.postMessage(style);
		return deferred.promise;
	}
	
	/**
	 * Installs a style file, getting the contents of an nsIFile and showing appropriate
	 * error messages
	 * @param {String|nsIFile} style An nsIFile representing a style on disk, or a string
	 *     containing the style data
	 * @param {String} origin The origin of the style, either a filename or URL, to be
	 *     displayed in dialogs referencing the style
	 */
	this.install = Zotero.Promise.coroutine(function* (style, origin) {
		var styleInstalled;
		
		if(style instanceof Components.interfaces.nsIFile) {
			// handle nsIFiles
			var url = Services.io.newFileURI(style);
			styleInstalled = Zotero.HTTP.promise("GET", url.spec).when(function(xmlhttp) {
				return _install(xmlhttp.responseText, style.leafName);
			});
		} else {
			styleInstalled = _install(style, origin);
		}
		
		styleInstalled.catch(function(error) {
			// Unless user cancelled, show an alert with the error
			if(typeof error === "object" && error instanceof Zotero.Exception.UserCancelled) return;
			if(typeof error === "object" && error instanceof Zotero.Exception.Alert) {
				error.present();
				error.log();
			} else {
				Zotero.logError(error);
				(new Zotero.Exception.Alert("styles.install.unexpectedError",
					origin, "styles.install.title", error)).present();
			}
		}).done();
	});
	<commit_msg>Fix style installation from file<commit_after>/*
    ***** 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 *****
*/

/**
 * @property {Boolean} cacheTranslatorData Whether translator data should be cached or reloaded
 *	every time a translator is accessed
 * @property {Zotero.CSL} lastCSL
 */
Zotero.Styles = new function() {
	var _initialized = false;
	var _styles, _visibleStyles;
	
	var _renamedStyles = null;
	
	Components.utils.import("resource://gre/modules/Services.jsm");
	Components.utils.import("resource://gre/modules/FileUtils.jsm");
	
	this.xsltProcessor = null;
	this.ns = {
		"csl":"http://purl.org/net/xbiblio/csl"
	};
	
	
	/**
	 * Initializes styles cache, loading metadata for styles into memory
	 */
	this.reinit = Zotero.Promise.coroutine(function* () {
		Zotero.debug("Initializing styles");
		var start = new Date;
		_initialized = true;
		
		// Upgrade style locale prefs for 4.0.27
		var bibliographyLocale = Zotero.Prefs.get("export.bibliographyLocale");
		if (bibliographyLocale) {
			Zotero.Prefs.set("export.lastLocale", bibliographyLocale);
			Zotero.Prefs.set("export.quickCopy.locale", bibliographyLocale);
			Zotero.Prefs.clear("export.bibliographyLocale");
		}
		
		_styles = {};
		_visibleStyles = [];
		this.lastCSL = null;
		
		// main dir
		var dir = Zotero.getStylesDirectory().path;
		var num = yield _readStylesFromDirectory(dir, false);
		
		// hidden dir
		var hiddenDir = OS.Path.join(dir, 'hidden');
		if (yield OS.File.exists(hiddenDir)) {
			num += yield _readStylesFromDirectory(hiddenDir, true);
		}
		
		// Sort visible styles by title
		_visibleStyles.sort(function(a, b) {
			return a.title.localeCompare(b.title);
		})
		// .. and freeze, so they can be returned directly
		_visibleStyles = Object.freeze(_visibleStyles);
		
		Zotero.debug("Cached " + num + " styles in " + (new Date - start) + " ms");
		
		// load available CSL locales
		var localeFile = {};
		var locales = {};
		var primaryDialects = {};
		var localesLocation = "chrome://zotero/content/locale/csl/locales.json";
		localeFile = JSON.parse(yield Zotero.File.getContentsFromURLAsync(localesLocation));
		
		primaryDialects = localeFile["primary-dialects"];
		
		// only keep localized language name
		for (let locale in localeFile["language-names"]) {
			locales[locale] = localeFile["language-names"][locale][0];
		}
		
		this.locales = locales;
		this.primaryDialects = primaryDialects;
		
		// Load renamed styles
		_renamedStyles = {};
		yield Zotero.HTTP.request(
			"GET",
			"resource://zotero/schema/renamed-styles.json",
			{
				responseType: 'json'
			}
		)
		.then(function (xmlhttp) {
			// Map some obsolete styles to current ones
			if (xmlhttp.response) {
				_renamedStyles = xmlhttp.response;
			}
		})
	});
	this.init = Zotero.lazy(this.reinit);
	
	/**
	 * Reads all styles from a given directory and caches their metadata
	 * @private
	 */
	var _readStylesFromDirectory = Zotero.Promise.coroutine(function* (dir, hidden) {
		var numCached = 0;
		
		var iterator = new OS.File.DirectoryIterator(dir);
		try {
			while (true) {
				let entries = yield iterator.nextBatch(10); // TODO: adjust as necessary
				if (!entries.length) break;
				
				for (let i = 0; i < entries.length; i++) {
					let entry = entries[i];
					let path = entry.path;
					let fileName = entry.name;
					if (!fileName || fileName[0] === "."
							|| fileName.substr(-4).toLowerCase() !== ".csl"
							|| entry.isDir) continue;
					
					try {
						let code = yield Zotero.File.getContentsAsync(path);
						var style = new Zotero.Style(code, path);
					}
					catch (e) {
						Components.utils.reportError(e);
						Zotero.debug(e, 1);
						continue;
					}
					if(style.styleID) {
						// same style is already cached
						if (_styles[style.styleID]) {
							Components.utils.reportError('Style with ID ' + style.styleID
								+ ' already loaded from ' + _styles[style.styleID].fileName);
						} else {
							// add to cache
							_styles[style.styleID] = style;
							_styles[style.styleID].hidden = hidden;
							if(!hidden) _visibleStyles.push(style);
						}
					}
					numCached++;
				}
			}
		}
		finally {
			iterator.close();
		}
		return numCached;
	});
	
	/**
	 * Gets a style with a given ID
	 * @param {String} id
	 * @param {Boolean} skipMappings Don't automatically return renamed style
	 */
	this.get = function (id, skipMappings) {
		if (!_initialized) {
			throw new Zotero.Exception.UnloadedDataException("Styles not yet loaded", 'styles');
		}
		
		if(!skipMappings) {
			var prefix = "http://www.zotero.org/styles/";
			var shortName = id.replace(prefix, "");
			if(_renamedStyles.hasOwnProperty(shortName) && _styles[prefix + _renamedStyles[shortName]]) {
				let newID = prefix + _renamedStyles[shortName];
				Zotero.debug("Mapping " + id + " to " + newID);
				return _styles[newID];
			}
		}
		
		return _styles[id] || false;
	}
	
	/**
	 * Gets all visible styles
	 * @return {Zotero.Style[]} - An immutable array of Zotero.Style objects
	 */
	this.getVisible = function () {
		if (!_initialized) {
			throw new Zotero.Exception.UnloadedDataException("Styles not yet loaded", 'styles');
		}
		return _visibleStyles; // Immutable
	}
	
	/**
	 * Gets all styles
	 *
	 * @return {Object} - An object with style IDs for keys and Zotero.Style objects for values
	 */
	this.getAll = function () {
		if (!_initialized) {
			throw new Zotero.Exception.UnloadedDataException("Styles not yet loaded", 'styles');
		}
		return _styles;
	}
	
	/**
	 * Validates a style
	 * @param {String} style The style, as a string
	 * @return {Promise} A promise representing the style file. This promise is rejected
	 *    with the validation error if validation fails, or resolved if it is not.
	 */
	this.validate = function(style) {
		var deferred = Zotero.Promise.defer(),
			worker = new Worker("resource://zotero/csl-validator.js"); 
		worker.onmessage = function(event) {
			if(event.data) {
				deferred.reject(event.data);
			} else {
				deferred.resolve();
			}
		};
		worker.postMessage(style);
		return deferred.promise;
	}
	
	/**
	 * Installs a style file, getting the contents of an nsIFile and showing appropriate
	 * error messages
	 * @param {String|nsIFile} style An nsIFile representing a style on disk, or a string
	 *     containing the style data
	 * @param {String} origin The origin of the style, either a filename or URL, to be
	 *     displayed in dialogs referencing the style
	 */
	this.install = Zotero.Promise.coroutine(function* (style, origin) {
		var styleInstalled;
		
		try {
			if (style instanceof Components.interfaces.nsIFile) {
				// handle nsIFiles
				var url = Services.io.newFileURI(style);
				var xmlhttp = yield Zotero.HTTP.request("GET", url.spec);
				styleInstalled = yield _install(xmlhttp.responseText, style.leafName);
			} else {
				styleInstalled = yield _install(style, origin);
			}
		}
		catch (e) {
			// Unless user cancelled, show an alert with the error
			if(typeof error === "object" && error instanceof Zotero.Exception.UserCancelled) return;
			if(typeof error === "object" && error instanceof Zotero.Exception.Alert) {
				error.present();
				error.log();
			} else {
				Zotero.logError(error);
				(new Zotero.Exception.Alert("styles.install.unexpectedError",
					origin, "styles.install.title", error)).present();
			}
		}
	});
	
{'fraction_non_alphanumeric': 0.12217496962332929, 'fraction_numerical': 0.0020656136087484813, 'mean_word_length': 3.11422144463884, 'pattern_counts': {'":': 2, '<': 7, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 4, 'xml': 10}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1670411', 'n_tokens_mistral': 5968, 'n_tokens_neox': 5078, 'n_words': 1400}