1 (изменено: Rumata, 2012-02-17 13:14:36)

Тема: JScript: модуль обработки INI файлов и утилита работы с INI файлами


Получилось большое сообщение. Поэтому в ходе ее написания я решил ее разделить на три отдельных части. Первая часть дает описание модуля, иллюстрируя его возможности примерами кода. Вторая часть - это текст самого модуля. Из текста модуля были удалены англоязычные комментарии. Приведена ссылка на полный текст модуля. Третья часть - описание и исходный текст полноценного приложения, разработанного в качестве иллюстрации работы модуля.

Описание модуля обработки INI файлов

Представляю сообществу модуль подготовки данных для чтения/записи в INI файлы. Этот пост отражает мое видение решения задачи - каждое действие должно отрабатываться на определенной стадии и должно существовать определенное "разделение труда" между функциями. То есть функции чтения и записи не должны выполнять какие-либо другие действия кроме работы с файловой системой. Верно и обратное, функции обработки данных не должны выполнять действий, связанных с файловой системой.

-- Это первое отличие данного решения от аналогичного, предложенного в теме JScript: чтение INI файлов - обработка данных и чтение/запись в файл разделены.
-- Второе отличие заключается в том, что здесь предлагается более гибкая реализация обработки данных. Так, данные могут поступать в функцию в виде большой строковой переменной, массива или функции, которая возвращает содержимое INI файла строка за строкой.
-- Третье отличие заключается в большей гибкости при работе с множественной вложенностью (будет рассмотрена далее).
-- Четвертое - код корректно обрабатывает одноименные ключи в одном разделе и при преобразовании в сохраняет их в виде массива. Аналогично, метод INI.stringify "знает" как обойтись с этим массивом.

Хотя при этом код увеличился примерно в 2 раза, по сравнению с ранее упомянутым решением (сравнение производилось над файлами с полностью удаленными комментариями, в качестве отступов использовались символы табуляции), однако скорость выполнения практически не пострадала, и существенно была улучшена функциональность модуля. Так файл объемом более 2МБ (примерно около 50 тыс. строк, это реальный файл) был обработан примерно за одинаковое время - около 1500-1900 мс.

Модуль предоставляет глобальный объект INI, содержащий два метода parse и stringify. Знакомые с JSON узнают названия этих методов - их функциональность аналогична. Отличие заключается в строковом представлении обрабатываемых объектов.


INI.stringify(value, levelDelimiter)

Метод берет объект value и преобразует в текстовое представление. Необязательный строковый параметр levelDelimiter задает символ, используемый для разделения имен вложенных узлов друг от друга. Значение по умолчанию - символ слэш - '/'.


INI.parse(text, levelDelimiter, deepen)

Метод преобразует текст, переданный параметром text в объект. Значение строкового параметра levlDelimiter аналогично предыдущему - задать символ, который разделяет имена вложенных узлов. Третий логический параметр, тоже необязательный, указывает как необходимость создания вложенной структуры.

Обычно INI файл это текстовый файл имеющий примерно такой вид:

Текст 1

[section1]

key1=value1
key2=value2


[section2]

key3=value3
key4=value4

который программно может быть преобразован в такую структуру:

Код 1

var iniObj = {
	section1: {
		key1: 'value1', 
		key2: 'value2'
	}, 
	section2: {
		key3: 'value3', 
		key4: 'value4'
	}
}

Верно и обратное - подобная структура может быть преобразована в текстовый формат без нарушения отношений между хранимыми элементами.

Рассмотрим пример.

Текст 2

[section1]

key1=value1
key2=value2


[section1/section2]

key3=value3
key4=value41
key4=value42


[section3]

key5=value5
key6=value6

Пусть содержимое этого текстового файла было считано в переменную iniTxt.


var iniTxt = <...>
var iniObj = INI.parse(iniTxt);

Этот код создаст объект такого вида. Для манипуляции с узлами и ключами необходимо использовать "скобочную" нотацию - iniObj['section1/section2']['key3']:

Код 2.1

var iniObj = {
	section1: {
		key1: 'value1', 
		key2: 'value2', 
	'section1/section2': {
		key3: 'value3', 
		key4: [
			'value41', 
			'value42'
		]
	}, 
	section3: {
		key5: 'value5', 
		key6: 'value6'
	}
}

Однако, при указании параметра deepen == true объект будет отличаться.


var iniTxt = <...>
var iniObj = INI.parse(iniTxt, '/', true);

И результат работы этого скрипта ниже. Для манипуляции с узлами и ключами можно использовать как "скобочную", так и "точечную" нотацию - iniObj.section1.section2.key3:

Код 2.2

var iniObj = {
	section1: {
		key1: 'value1', 
		key2: 'value2', 
		section2: {
			key3: 'value3', 
			key4: [
				'value41', 
				'value42'
			]
		}
	}, 
	section3: {
		key5: 'value5', 
		key6: 'value6'
	}
}

Как было сказано ранее - параметр text метода INI.parse может принимать один из трех возможных типов данных - строку, массив или функцию.

Передаваемая строка разбивается делится построчно по символу "перевод строки" на отдельные строки и обрабатывается строка за строкой.


var iniObj = INI.parse('...');

Массив содержит отдельные строки. Метод обходит все строки и разбирает их в объект.


var iniObj = INI.parse(['...', '...' ...]);

Функция - это генератор строк, который должен вернуть значение null как индикатор окончания входных данных. Этот способ может быть полезен при обработке большого или непрерывного потока данных. Далее полный пример, поясняющий принцип работы функции


function readIniFile(filename)
{
	var fso = new ActiveXObject('Scripting.FileSystemObject');
	var f = fso.GetFile(filename);
	var h = f.OpenAsTextStream(1);

	iniObj = INI.parse(function()
	{
		return h.AtEndOfStream ? null : h.ReadLine();
	});

	h.Close();
};
( 2 * b ) || ! ( 2 * b )

2 (изменено: Rumata, 2012-02-20 18:46:43)

Re: JScript: модуль обработки INI файлов и утилита работы с INI файлами

Код модуля

Далее - исходный код модуля с полностью удаленными комментариями. Полный текст (включая комментарии на английском) располагается по этой ссылке - http://code.google.com/p/jsxt/source/br … /js/INI.js


var INI = INI || {};
(function() {
	var validateDelimiter = function(delimiter) {
		delimiter = String(delimiter || '/').charAt(0);
		if (delimiter == '[' || delimiter == ']' || delimiter <= ' ') {
			throw new Error('Illegal delimiter: "' + delimiter + '"');
		}
		return delimiter;
	};
	INI.stringify = function(value, levelDelimiter) {
		levelDelimiter = validateDelimiter(levelDelimiter);
		var walk = function(result, value, upperLevel) {
			var section = [];
			for (var p in value) {
				if (!value.hasOwnProperty(p)) {
					continue;
				}
				var v = value[p];
				var t = Object.prototype.toString.call(v);
				if (t == '[object Object]') {
					section.push('\n\n[' + upperLevel + p + ']\n');
					walk(section, v, upperLevel + p + levelDelimiter);
					continue;
				}
				v = [].concat(v);
				for (var i = 0; i < v.length; i++) {
					result.push(p + '=' + v[i]);
				}
			}
			result.push.apply(result, section);
		};
		var result = [];
		walk(result, value, '');
		return result.join('\n');
	};
	INI.parse = function(text, levelDelimiter, deepen) {
		levelDelimiter = validateDelimiter(levelDelimiter);
		var result = {};
		var ptr = result;
		var setPtr = (function() {
			if (!deepen) {
				return function(section) {
					if (!result.hasOwnProperty(section)) {
						result[section] = {};
					}
					ptr = result[section];
				}
			}
			return function(section) {
				var t = section.split(levelDelimiter);
				ptr = result;
				for (var i = 0; i < t.length; i++) {
					var u = t[i];
					if (!u) {
						continue;
					}
					if (!ptr.hasOwnProperty(u)) {
						ptr[u] = {};
					}
					ptr = ptr[u];
				}
			}
		})();
		var nextLine = (function() {
			if (typeof text == 'function') {
				return text;
			}
			if (Object.prototype.toString.call(text) == '[object Array]') {
				var n = text.length;
				var i = 0;
				return function() {
					return n-- ? text[i++] : null;
				};
			}
			var re = /[^\r\n]+/g;
			return function() {
				var r = re.exec(text);
				return r && r[0];
			}
		})();
		var reSec = /^\s*\[([^\[\]]+)\]\s*$/;
		var reKey = /^\s*([^;#\s][^=]*?)\s*=([^\r\n]*?)$/;
		var line, m, k, v;
		while ((line = nextLine()) != null) {
			m = line.match(reSec);
			if (m) {
				setPtr(m[1]);
				continue;
			}
			m = line.match(reKey);
			if (m) {
				k = m[1];
				v = m[2];
				if (!ptr.hasOwnProperty(k)) {
					ptr[k] = v;
				} else {
					if (typeof ptr[k] == 'string') {
						ptr[k] = [ptr[k]];
					}
					ptr[k].push(v);
				}
				continue;
			}
		}
		return result;
	};
})();
( 2 * b ) || ! ( 2 * b )

3 (изменено: Rumata, 2012-02-20 18:47:32)

Re: JScript: модуль обработки INI файлов и утилита работы с INI файлами

Утилита работы с INI файлами

Данная утилита разработана на основе модуля JScript: модуль подготовки данных для ч …  INI файлы.

Программа читает INI файл в заданной кодировке (/D - по умолчанию, /U - Unicode, или /A - ASCII) и
-- выводит список всех ключей и разделов первого уровня

inifile filename /L

-- выводит список ключей и подразделов указанного раздела

inifile filename /L /S:section

-- выводит значение хранимое в ключе

inifile filename /S:section /K:key

Утилита ищет разделы и ключи и выводит на экран. Если искомое не найдено - выводится соответствующее сообщение на устройство вывода STDERR и программа возвращает системе код завершения 1.

В сети существует бинарный аналог INITOOL (http://www.optimumx.com/downloads.html#iniTool), который не смог обработать конфигурационный файл размером более 2МБ вывел сообщение "More data is available than the memory buffer had allocated.". Данная же программа мгновенно читает маленькие файлы, но чтение огромных (около 2 МБ) файлов занимает примерно 1.5-2 секунды. При этом программа не падает и успешно обрабатывает такие файлы. Хотя это и не является большим плюсом, но все же.

Программа построена по принципу 2-в-1, то есть она содержит код, выполняемый как пакетный файл и jscript-программу. Поэтому текст js-скрипта надо сохранить в файл с именем inifile.bat и запускать как обычный BAT-файл.


@set @x=0/*!&&@set @x=
@ %windir%\System32\cscript.exe //nologo //e:javascript "%~dpnx0" %*
@goto :eof */

//
// inifile
// Command line tool for working with ini-files
//
// Copyright (c) 2012, Ildar Shaimordanov
//

function help()
{
	alert([
		'IniFile Version 0.3 Beta', 
		'Copyright (C) 2012 Ildar Shaimordanov', 
		'', 
		'Usage: ' + WScript.ScriptName + ' OPTIONS filename', 
		'', 
		'    /D       - Opens the file using the system default', 
		'    /U       - Opens the file as Unicode', 
		'    /A       - Opens the file as ASCII', 
		'    /S:value - Specifies the section name', 
		'    /K:value - Specifies the key name', 
		'    /L       - List sections of keys of the specified section', 
	].join('\n'));
};

function alert(value)
{
	WScript.Echo(value);
};

function quit(exitCode)
{
	WScript.Quit(exitCode);
};

function error(value)
{
	WScript.StdErr.WriteLine(value);
	quit(1);
};

var uArgs = WScript.Arguments.Unnamed;
var nArgs = WScript.Arguments.Named;

if ( uArgs.length != 1 || WScript.FullName.match(/wscript\.exe/i) ) {
	help();
	quit(1);
}

///////////////////////////////////////////////////////////////////////////
//js/INI.js

var INI=INI||{};(function(){var validateDelimiter=function(delimiter){delimiter=String(delimiter||'/').charAt(0);if(delimiter=='['||delimiter==']'||delimiter<=' '){throw new Error('Illegal delimiter: "'+delimiter+'"');}return delimiter;};INI.stringify=function(value,levelDelimiter){levelDelimiter=validateDelimiter(levelDelimiter);var walk=function(result,value,upperLevel){var section=[];for(var p in value){if(!value.hasOwnProperty(p)){continue;}var v=value[p];var t=Object.prototype.toString.call(v);if(t=='[object Object]'){section.push('\n\n['+upperLevel+p+']\n');walk(section,v,upperLevel+p+levelDelimiter);continue;}v=[].concat(v);for(var i=0;i<v.length;i++){result.push(p+'='+v[i]);}}result.push.apply(result,section);};var result=[];walk(result,value,'');return result.join('\n');};INI.parse=function(text,levelDelimiter,deepen){levelDelimiter=validateDelimiter(levelDelimiter);var result={};var ptr=result;var setPtr=(function(){if(!deepen){return function(section){if(!result.hasOwnProperty(section)){result[section]={};}ptr=result[section];}}return function(section){var t=section.split(levelDelimiter);ptr=result;for(var i=0;i<t.length;i++){var u=t[i];if(!u){continue;}if(!ptr.hasOwnProperty(u)){ptr[u]={};}ptr=ptr[u];}}})();var nextLine=(function(){if(typeof text=='function'){return text;}if(Object.prototype.toString.call(text)=='[object Array]'){var n=text.length;var i=0;return function(){return n--?text[i++]:null;};}var re=/[^\r\n]+/g;return function(){var r=re.exec(text);return r&&r[0];}})();var reSec=/^\s*\[([^\[\]]+)\]\s*$/;var reKey=/^\s*([^;#\s][^=]*?)\s*=([^\r\n]*?)$/;var line,m,k,v;while((line=nextLine())!=null){m=line.match(reSec);if(m){setPtr(m[1]);continue;}m=line.match(reKey);if(m){k=m[1];v=m[2];if(!ptr.hasOwnProperty(k)){ptr[k]=v;}else{if(typeof ptr[k]=='string'){ptr[k]=[ptr[k]];}ptr[k].push(v);}continue;}}return result;};})();

///////////////////////////////////////////////////////////////////////////

var filename = uArgs.item(0);
var format = nArgs.Exists('D') ? -2 : nArgs.Exists('U') ? -1 : 0;

var iniObj = (function()
{
	var fso = new ActiveXObject('Scripting.FileSystemObject');
	var e;
	try {
		var f = fso.GetFile(filename);
	} catch (e) {
		error(e.message + ': ' + filename);
	}
	var h = f.OpenAsTextStream(1, format);

	var iniTxt = h.ReadAll();
//	var iniTxt = iniTxt.split(/\r\n|\r|\n/);
	var result = INI.parse(iniTxt);
//	var result = INI.parse(function()
//	{
//		return h.AtEndOfStream ? null : h.ReadLine();
//	});

	h.Close();

	return result;
})();


var list = nArgs.Exists('L');
var section = nArgs.item('S');
var key = nArgs.item('K');

if ( section ) {
	if ( ! iniObj.hasOwnProperty(section) || Object.prototype.toString.call(iniObj[section]) != '[object Object]' ) {
		error('Section not found: ' + section);
	}
	iniObj = iniObj[section];
}

if ( list ) {
	for (var p in iniObj) {
		if ( ! iniObj.hasOwnProperty(p) ) {
			continue;
		}
		alert(p);
	}
	quit();
}


if ( ! key ) {
	error('Empty key was passed');
}

if ( ! iniObj.hasOwnProperty(key) ) {
	error('Key not found: ' + key);
}

var value = [].concat(iniObj[key]).join('\n');

alert(value);
( 2 * b ) || ! ( 2 * b )