1 (изменено: Rumata, 2012-04-06 02:39:48)

Тема: HTA/JScript: Автоматический разбор параметров командной строки

Начало разговора в теме JavaScript: форматирование числа. Предлагаю продолжить обсуждение здесь.

Задача ставится следующим образом. Необходимо разобрать командную строку, с помощью которой было запущено приложение HTA. Известно, что WSH приложения имею мощный интерфейс для работы с параметрами командной строки как со всеми аргументами, так и с отдельными типами (именованные или неименованные):

// все аргументы
var args = WScript.Arguments;
for (var i = 0; i < args.length; i++) {
	var arg = args.item(i);
	WScript.Echo(arg);
}

// неименованные, или безымянные аргументы
var uArgs = WScript.Arguments.Unnamed;
for (var i = 0; i < uArgs.length; i++) {
	var arg = uArgs.item(i);
	WScript.Echo(arg);
}

// именованные аргументы
var nArgs = WScript.Arguments.Named;
for (var e = new Enumerator(nArgs); ! e.atEnd(); e.moveNext()) {
	var k = e.item(i);
	var v = nArgs.item(k);
	WScript.Echo(k + ' = ' + v);
}

Как видно, именованные аргументы имеют несколько отличный способ доступа к самим значениям, но это не вызывает проблемы. Видно, что аргументы "унифицируются" - начальные и конечные кавычки удаляются (хотя удаляются, на мой взгляд, не совсем корректно и существует одна проблема с именованными аргументами, о которой можно поговорить отдельно).

Однако HTML-приложения таких удобных средств для работы с командной строкой не имеют. Существует только свойство commandLine строкового типа доступное из тега <hta:application>. То есть все аргументы в одной куче. Разработчику самому предоставляется возможность решать - как ему интерпретировать переданную информацию.

В ходе обсуждения в теме предложен способ, который позволяет решить данную проблему:


var re = /"(.*?)"(?:\s+|$)|([\S]+)/g;
var r;
while ( r = re.exec(s) ) {
	var arg = r[1] || r[2];
	alert(arg);
}

Плюсами данного решения являются:
-- лаконичность
-- универсальность

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

Очевидно, что в погоне за универсальностью чаще всего создается монстр; вероятно, что данное решение содержит недочеты, которые не были обнаружены изначально, и могут всплыть позже.

Интерпретация аргументов:

строковые аргументы:
строка - простая строка. не содержащая пробелы
"строка" - простая строка, допускаются пробелы и кавычки

простые именованные аргументы:
/ключ - значение ключа - пустая срока, проверяется наличие ключа

логические ключи:
/ключ- - значение ключа false
/ключ+ - значение ключа true

строковые аргументы:
/ключ:значение - значение ключа - строка без пробелов и кавычек
/ключ:"значение" - значение ключа - любая строка символов, включая пробелы и кавычки

В результате работы создается глобальный объект HTA, содержащий несколько свойств и методов

appFullName
Возвращает полный путь текущего приложения.

appPath
Возвращает только путь к приложению.

appName
Возвращает только имя файла текущего приложения.

cmdLine
Возвращает командную строку.

arguments
Возвращает полный список всех аргументов, переданных приложению.

Методы: item(index), length()

arguments.unnamed
Список строковых аргументов в виде массива.

Методы: item(index), length()

arguments.named
Возвращает объект-список именованных аргументов.
Каждый ключ это массив значений, даже если передано было одно значение.
Таким образом можно передать любое количество одноименных ключей.

Именованный аргумент может быть простым, логическим или строковым.

Логический аргумент передается приложению как /ключ+, чтобы
передать значение true или /ключ-, чтобы передать значение false.

Простой именованный аргумент не имеет значений и передается как /ключ.

Строковый аргумент передается приложению как пара /ключ:значение.

Методы: exists(key), item(key), keys(), length()

.
Ниже приведен тестовый файл, который выводит все аргументы, переданные скрипту. Для проверки необходимо поэкспериментировать с разным набором и количеством аргументов.

test.hta


<html>
<head>
<script src="HTA.js"></script>
</head>
<body>
<pre>
<script>

document.writeln('Full name    : ', HTA.appFullName);
document.writeln('Path name    : ', HTA.appPath);
document.writeln('File name    : ', HTA.appName);
document.writeln('Command Line : ', HTA.cmdLine);

function printArgs(name, args)
{
	var keys = args.keys ? args.keys() : [];

	var len = keys.length || args.length();

	document.writeln();
	document.writeln('<h3>', name + ' (<code>' + len + '</code>)</h3>');
	for (var i = 0; i < len; i++) {
		var k = keys[i] || i;
		document.writeln(k, ' : ', args.item(k));
	}
};

printArgs('Common',  HTA.arguments);
printArgs('Unnamed', HTA.arguments.unnamed);
printArgs('Named',   HTA.arguments.named);

</script>
</pre>
</body>
</html>

.
HTA.js



(function()
{
	// Duck-typing check for MSIE engine
	if ( this !== this.window ) {
		return;
	}

	var d = this.document;
	if ( ! d || ! d.all || ! d.getElementsByTagName || ! d.createElement || ! d.insertBefore ) {
		return;
	}

	// Create the hta:application tag if it is necessary
	var htApp = d.getElementsByTagName('application')[0];
	if ( ! htApp ) {
		htApp = d.createElement('hta:application');
		var head = d.getElementsByTagName('head')[0];
		head.insertBefore(htApp, head.firstChild);
	}

	// Only HTAs have this property
	var cmdLine = htApp.commandLine;
	if ( ! cmdLine ) {
		return;
	}

	var re = new RegExp([
		// named arguments rounded or not by the quote characters
		// 	/named
		// where the 'named' term stands for one of the following:
		// 	/key+		key is true
		// 	/key-		key is false
		// 	/key
		// 	/key:
		// 	/key:""		key is an empty string
		// 	/key:value
		// 	/key:"value"	key contains the string 'value'
		'(', 
			'\\/', 
			'([\\S]+?)', 
			'(?:', 
				// /key+
				'(\\+)', 

				'|', 

				// /key-
				'(\\-)', 

				'|', 

				// /key:"value"
				// /key:""
				':"(.*?)"', 

				'|', 

				// /key:value
				// /key:
				':([\\S]*)', 
			')?', 

			'|', 

			// unnamed arguments rounded by the quote characters
			// 	"value"
			'"(.*?)"', 

			'|', 

			// unnamed arguments without the quote characters
			// 	value
			'([\\S]+)', 
		')', 
		'(?:\\s+|$)', 
	].join(''), 'g');

	var r;

	// It's impossible but we are paranoic
	r = re.exec(cmdLine);
	if ( ! r ) {
		return;
	}

	var fullname = (r[7] || r[8]).replace(/"/g, '');
	var filename = fullname.replace(/^.+\\/, '');
	var pathname = fullname.replace(/\\[^\\]+$/, '');

	var args = [];

	var unnamed = [];

	var named = {};
	var named_length = 0;
	var named_keys = [];

	while ( r = re.exec(cmdLine) ) {
		var k = r[2];
		var v = r[3] ? true : r[4] ? false : r[5] || r[6] || r[7] || r[8];

		if ( ! k && ! v ) {
			continue;
		}

		args.push(r[1]);

		if ( ! k ) {
			unnamed.push(v);
			continue;
		}

		named_length++;
		named[k] = (named[k] || []).concat(v);
		(function()
		{
			for (var i = 0; i < named_keys.length; i++) {
				if ( k == named_keys[i] ) {
					return;
				}
			}
			named_keys.push(k);
		})();
	}

	var HTA = {
		arguments: {
			named: {
				exists: function(key)
				{
					return named.hasOwnProperty(key);
				}, 
				item: function(key)
				{
					return named[key];
				}, 
				keys: function()
				{
					return [].concat(named_keys);
				}, 
				length: function()
				{
					return named_keys.length;
				}
			}, 
			unnamed: {
				item: function(index)
				{
					return unnamed[index];
				}, 
				length: function()
				{
					return unnamed.length;
				}
			}, 
			item: function(index)
			{
				return args[index];
			}, 
			length: function()
			{
				return args.length;
			}
		}, 
		appFullName: fullname, 
		appPath: pathname, 
		appName: filename, 
		cmdLine: cmdLine
	};

	this.HTA = HTA;
})();
( 2 * b ) || ! ( 2 * b )

2

Re: HTA/JScript: Автоматический разбор параметров командной строки

Rumata пишет:

…и создал монстра - модуль более чем в 250 строк кода, комментариев и пустых строк, претендующий на универсальность.

Ну, монстр и монстр , подумаешь… Если действительно универсальный и работает — это не страшно, главное, чтобы его можно было прозрачно (т.е. — никак не затрагивая ничего «снаружи») подключать к своему HTA.

P.S. Я обычно пишу на VBScript. Смогу ли я использовать данный функционал совместно с основным кодом, написанным на VBScript?

3 (изменено: Rumata, 2012-04-04 18:03:14)

Re: HTA/JScript: Автоматический разбор параметров командной строки

alexii пишет:

Я обычно пишу на VBScript. Смогу ли я использовать данный функционал совместно с основным кодом, написанным на VBScript?

Да. С небольшими оговорками.
1. Методы должны указываться явно, в форме объект.метод(). Опуская круглые скобки вы сломаете код, так как объект.метод вернет текстовое представление метода, а не результат его выполнения.
2. Метод HTA.arguments.named.keys() возвращает список ключей в виде массива. Однако в VBS этот строка имен ключей, разделенных запятыми. Видимо это издержки разного представления массивов в JS и VBS.

Ниже приведен "перевод" примера, выполненного с JS на VBS.


<html>
<head>
<script src="HTA.js"></script>
</head>
<body>
<pre>
<script language=vbscript>

document.writeln "Full name    : ", HTA.appFullName
document.writeln "Path name    : ", HTA.appPath
document.writeln "File name    : ", HTA.appName
document.writeln "Command Line : ", HTA.cmdLine

sub printArgs(name, args)
	dim keys
	dim L

	on error resume next
	keys = array()
	keys = split(args.keys(), ",")

	L = args.length() - 1

	document.writeln
	document.writeln "<h3>", name & " (<code>" & (L + 1) & "</code>)</h3>"

	dim k, v
	for i = 0 to L
		k = i
		k = keys(i)

		v = args.item(k)
		document.writeln k, " : ", v
	next
	on error goto 0
end sub

printArgs "Common",  HTA.arguments
printArgs "Unnamed", HTA.arguments.unnamed
printArgs "Named",   HTA.arguments.named

</script>
</pre>
</body>
</html>

.
P.S.:
Исправил одну строку кода библиотеки в первом сообщении, так как из-за опечатки неправильно определялась длина массива именованных аргументов.
Убрал одну лишнюю строку из определения регулярного выражения.

( 2 * b ) || ! ( 2 * b )

4

Re: HTA/JScript: Автоматический разбор параметров командной строки

Небольшое улучшение, отразившееся в значениях, сохраняемых в HTA.arguments. Теперь этот объект хранит каждый аргумент в "сыром" виде, включая имеющиеся кавычки на концах аргументов. Исправление введено для того чтобы
-- различать внутри HTA.arguments именованные аргументы вида /key:value от безымянных аргументов вида "/key:value"
-- все-таки знать (на всякий случай) в каком виде пришли данные, - например, /key, key: или /key:""

В остальном реализация и примеры на js и vbs рабочие. Чтобы не множить сущности, поправил только код файла HTA.js в первом сообщении.

( 2 * b ) || ! ( 2 * b )

5

Re: HTA/JScript: Автоматический разбор параметров командной строки

Rumata, а почему просто не воспользоваться функцией CreateWindow из сценария для порождения окна HTA? Ведь тот подход даст использование WScript, в том числе корректную обработку командной строки. Зачем городить монстра на 250 строк, если можно и без него обойтись?

6 (изменено: alexii, 2012-04-06 19:58:28)

Re: HTA/JScript: Автоматический разбор параметров командной строки

JSman пишет:

…в том числе корректную обработку командной строки.

Чьей командной строки?

7

Re: HTA/JScript: Автоматический разбор параметров командной строки

JSman
Зачем? Есть файл, назовем его test.hta, готовое HTML-приложение, которое запускается с дополнительными аргументами. Вот эти аргументы и разбираются.

alexii пишет:

Чьей командной строки?

HTML-приложения

( 2 * b ) || ! ( 2 * b )

8

Re: HTA/JScript: Автоматический разбор параметров командной строки

Rumata, приношу свои извинения, я неверно приписал Вам процитированное, вместо JSman. Ему же был адресован вопрос.

Исправил.

9

Re: HTA/JScript: Автоматический разбор параметров командной строки

alexii, еще раз постараюсь пояснить:
Rumata предлагает решение из "250 строк" для HTA - компонент, который делает то же самое, что и встроенный объект WScript.Arguments, который обеспечивает корректную обработку параметров командной строки.
Я предложил не использовать компонент, а внедрить код HTA в JS или WSF. Ведь уже написана замечательная функция CreateWindow, которая генерирует окно HTA.

10 (изменено: Rumata, 2012-04-07 13:11:30)

Re: HTA/JScript: Автоматический разбор параметров командной строки

Кстати пришло время рассказать про баги в WSCript.Arguments.Named

Rumata пишет:

существует одна проблема с именованными аргументами, о которой можно поговорить отдельно

Запустите сценарий, передав ему одноименный ключ дважды (можно и более), но с разными значениями.
cscript test.js /key:value1 /key:value2

var named = WScript.Arguments.Named;

WScript.Echo(named.Count);
WScript.Echo(named.length);

for (var e = new Enumerator(named); ! e.atEnd(); e.moveNext()) {
    var k = e.item();
    var v = named.item(k);
    WScript.Echo(k + ' = ' + v);
}

Так уж видимо рэдмондские волшебники решили, что WScript.Arguments.Named должен хранить первый найденный аргумент. Почему не последний? Почему не все одноименные, в виде массива?

JSman пишет:

Я предложил <...> внедрить код HTA в JS или WSF.

Да. Это тоже решение. Но не возникает ли дискомфорта при мысли где хранить разметку, стили, внешние файлы - все естественное для html-файлов и чужеродное в этих js/wsf файлах?

Ну и про 250 строк - там и код, и пустые строки, и обширные комментарии. Обфускатор все это сокращает до одной строки )))

( 2 * b ) || ! ( 2 * b )

11

Re: HTA/JScript: Автоматический разбор параметров командной строки

Rumata пишет:

Да. Это тоже решение. Но не возникает ли дискомфорта при мысли где хранить разметку, стили, внешние файлы - все естественное для html-файлов и чужеродное в этих js/wsf файлах?

Да нет, не возникает. Если рассматривать JS, то можно успешно хранить в комментриях (как-то писал об этом), если WSF - тег Resource.

Rumata пишет:

Запустите сценарий, передав ему одноименный ключ дважды (можно и более), но с разными значениями.
cscript test.js /key:value1 /key:value2

Ну почему баг? Ключ - это тоже идентификатор, а если бы значение было бы массивом, то нам бы пришлось постоянно проверять значение на тип.

12

Re: HTA/JScript: Автоматический разбор параметров командной строки

JSman, теперь понял. Я думал, что Вы имеете в виду обратный процесс — получение WScript изнутри HTA.

13

Re: HTA/JScript: Автоматический разбор параметров командной строки

А что касается монстра, Вы-то уж знаете, что прекрасно понимаю, что 250 строк - это не ужас. Я поэтому их взял в кавычки. Я намекал об объеме кода что ли, лишних вычислениях тогда, кода можно пойти простым путем.

14 (изменено: Rumata, 2012-04-07 15:17:08)

Re: HTA/JScript: Автоматический разбор параметров командной строки

JSman
В обеих темах мы участвовали вместе. И я хорошо знаю, что они представляют. Не уверен, что использование CreateWindow и GetResource в данном случае оправданно более простой путь.

( 2 * b ) || ! ( 2 * b )