Archive

Archive for the ‘Наше ПО’ Category

Русский Язык. Выбор окончаний

April 9th, 2010 Dmitriy Lyapin No comments

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

Например: "Комментариев: 4", "Новых писем: 1", "Всего сотрудников: 19" и т.д.

Пользователь - существо, как правило, неприхотливое. Для пользователя программа - магия. И если она работает, то уже хорошо, главное - покажите куда нажимать.

Но если вы все же найдете свободную минутку, чтобы его порадовать, то почему бы не сделать так: "4 комментария", "1 новое письмо", "Всего 19 сотрудников" и т.д.

В английском языке все просто: "1 comment", "N comments" (N > 1). Богатство же Русского Языка не дает скучать хмурым программистам. Статья посвящена правильному выбору окончаний.

Давайте возьмем слово "сообщение" и пробежимся по первой десятке чисел.

  • 0 сообщений
  • 1 сообщение
  • 2 сообщения
  • 3 сообщения
  • 4 сообщения
  • 5 сообщений
  • 6 сообщений
  • 7 сообщений
  • 8 сообщений
  • 9 сообщений

Можно выделить три группы, я назову их w1, w2 и w5.

Группа w1:

  • 1 сообщение

Группа w2:

  • 2 сообщения
  • 3 сообщения
  • 4 сообщения

Группа w5:

  • 5 сообщений
  • 6 сообщений
  • 7 сообщений
  • 8 сообщений
  • 9 сообщений
  • 0 сообщений

Для любого числа подходящая пара будет относится к одной из этих трех групп. Но как обобщить правило для всех чисел?

Первая мысль, которая приходит в голову - брать последнюю цифру. Действительно: "1021 сообщение", "542 сообщения", "35 сообщений". Но эта мысль верна ровно на 90%.

Дело в том, что все числа, предпоследняя цифра которых равна единице, отностяся к группе w5: "11 сообщений", "1012 сообщений".

Теперь можно сформулировать правило выбора группы для любого числа:

  • w5, если предпоследняя цифра равна единице
  • или w5, если последняя цифра больше или равна пяти
  • или w5, если последняя цифра равна нулю
  • или w1, если последняя цифра равна единице
  • иначе w2

Ниже несколько реализаций функции.

PHP:

//
// Функция выбирает нужное слово для конкретного числа.
// Например: 1 сообщение, 2 сообщения, 5 сообщений.
//
function NumberOf($number, $w1, $w2, $w5, $appendNumber = true)
{
	$n1 = $number % 10;   // 123 % 10 = 3
	$n12 = $number % 100; // 123 % 100 = 23
 
	if ($n1 >= 5 || $n1 == 0 || ($n12 >= 11 && $n12 <= 19))
		$w = $w5;
	else if ($n1 == 1)
		$w = $w1;
	else
		$w = $w2;
 
	return $appendNumber ? "$number $w" : $w;
}

Пример использования:

$n = 10;
 
echo NumberOf($n, 'новое сообщение', 'новых сообщения', 'новых сообщений');
 
echo "<b>$n</b>" .
       NumberOf($n, 'новое сообщение', 'новых сообщения', 'новых сообщений', false);

Результат:

10 новых сообщений
<b>10</b> новых сообщений

Java Script:

//
// Функция выбирает нужное слово для конкретного числа.
// Например: 1 сообщение, 2 сообщения, 5 сообщений.
//
function numberOf(number, w1, w2, w5, appendNumber)
{
	var w = '';
	var n1 = number % 10;   // 123 % 10 = 3
	var n12 = number % 100; // 123 % 100 = 23
 
	if (n1 >= 5 || n1 == 0 || (n12 >= 11 && n12 <= 19))
		w = w5;
	else if (n1 == 1)
		w = w1;
	else
		w = w2;
 
	if (appendNumber == null)
		appendNumber = true;
 
	return appendNumber ? (number + ' ' + w) : w;
}

Пример использования:

var n = 10;
alert numberOf(n, 'новое сообщение', 'новых сообщения', 'новых сообщений');
alert numberOf(n, 'новое сообщение', 'новых сообщения', 'новых сообщений', false);

C#:

//
// Функция выбирает нужное слово для конкретного числа.
// Например: 1 сообщение, 2 сообщения, 5 сообщений.
//
static string NumberOf(int number,
                       string w1,
                       string w2,
                       string w5,
                       bool appendNumber)
{
	string w = "";
	int n1 = number % 10;   // 123 % 10 = 3
	int n12 = number % 100; // 123 % 100 = 23
 
	if (n1 >= 5 || n1 == 0 || (n12 >= 11 && n12 <= 19))
		w = w5;
	else if (n1 == 1)
		w = w1;
	else
		w = w2;
 
	return appendNumber ? (number.ToString() + ' ' + w) : w;
}

Пример использования:

string s1, s2;
int n = 10;
 
s1 = NumberOf(n, "новое сообщение", "новых сообщения", "новых сообщений", true);
s2 = NumberOf(n, "новое сообщение", "новых сообщения", "новых сообщений", false);
 
Console.WriteLine(s1);
Console.WriteLine(s2);

Материал не претендует на революционность, хотя и является собственной разработкой. Я не искал решений в Интеренете, так как люблю изобретать велосипеды )

Автоматизация подписки на рассылку FeedBurner. Часть 3

March 9th, 2010 Alex Nikitin No comments

Заключительная часть трилогии о том, как взять feedburner’овскую капчу, заставить пользователя ввести её и подписать его на рассылку feedburner.

Часть 1, Часть 2

В этой статье разберем серверную часть программы – php-скрипт, задача которого обратиться к сервису feedburner, получить капчу, передать пользователю, принять расшифровку капчи от пользователя и передать feedburner. Я использовал ZendFramework, но в основном по мелочи.

Итак, весь скрипт состоит из двух больших блоков. Первый – для случая, когда мы только получаем капчу, второй – для случая, когда пользователь её уже расшифровал.

Когда мы обращаемся к фидбернеру в первый раз отрабатывает следующий скрипт:

$client = new Zend_Http_Client(/* здесь должна идти строка с адресом рассылки, которую генерирует feedburner */);
$client->setParameterPost(array(
	        'loc'  => 'ru_RU',
	        'uri'   => /* данные вашей рассылки */,
	        'email' => /* данные вашей рассылки */,
	    ));
$resp = $client->request('POST');	// выполняем POST запрос
$headers = $resp->getHeaders();
 
// меняем относительные пути на полные
$response =  str_replace('captcha?','http://feedburner.google.com/fb/a/captcha?', $resp);
$response =  str_replace('="/fb','="http://feedburner.google.com/fb', $response);
 
// В ответ на наш запрос feedburner генерирует форму
// для заполнения с рядом скрытых (hidden) полей.
// Значение этих полей нам нужно достать и отправить
// обратно фидбернеру при следующем обращении
// Здесь я опушу большую часть однотипных действий
$pos1 = strpos($response, 'action="');
$action = substr($response, $pos1 + 8);
$pos11 = strpos($action, '"');
$action = substr($action, 0, $pos11);
 
$pos1 = strpos($response, 'input type="hidden" name="uri"');
$uri = substr($response, $pos1 + 38);
$pos11 = strpos($uri, '"');
$uri = substr($uri, 0, $pos11);

А далее нам остается только сформировать собственный кусок кода HTML, который AJAX вернет клиенту:

<form>
	<input type="hidden" id="fbaction" name="fbaction" value=""/>
	<input type="hidden" id="fburi" name="fburi" value=""/>
	<input type="hidden" id="fbemail" name="fbemail" value=""/>
	<input type="hidden" id="fbtoken" name="fbtoken" value=""/>
	<input type="hidden" id="fbcookie" name="fbcookie" value=""/>
 
</form>

$action, $uri, $email, $token – то, что мы достали из кода, сгенерированного фидбернером. $headers[‘Set-cookie’] – куки которые, установил фидбернер, их также нужно передать.

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

// клиент для HTTP-запросов
$client = new Zend_Http_Client($action);
// устанавливаем параметры, которые получили в прошлый
// раз от фидбернера
$client->setParameterPost(array(
		'loc'  => 'ru_RU',
	        'token'  => $_GET['token'],
	        'uri'   => $_GET['uri'],
	        'captcha'   => $_GET['captcha'],
	        'email' => $_GET['email']
	    ));
// не забываем добавить куки
$cookie = Zend_Http_Cookie::fromString($_GET['cookie']);
$client->setCookie($cookie);
// делаем POST запрос к фидбернеру
$response = $client->request('POST');

Вот и все. В $response у нас попадет ответ от фидбернера. Он скажет, что, либо регистрация прошла успешно, либо мы неправильно ввели капчу, либо плохой e-mail и т.п. Я ничего лучше не придумал, как проверять полученный текст на наличие каких-то ключевых слов. Это, в общем-то, тривиальная задача.

Автоматизация подписки на рассылку FeedBurner. Часть 2

February 17th, 2010 Alex Nikitin No comments

В первой части я описал, зачем нам в принципе автоматизировать подписку FeedBurner. Коротко: автоматизация заключается в автоматической подписке электронной почты пользователя на рассылку FeedBurner при заполнении пользователем формы подписки на рассылку другого сервиса. В нашем примере используется сервис smartresponder.ru.

В этой статье я приведу javascript код клиентской части скрипта. Напомню, для взаимодействия с сервером используется технология AJAX. Также я использовал фреймворк JQuery.

Общий принцип взаимодействия таков. Javascript вызывает серверный PHP скрипт с помощью http-запросов с методом GET и передает туда все необходимые параметры. Серверный скрипт взаимодействует с сервисом FeedBurner и формирует html-документ, который возвращает клиентскому скрипту. Клиентский скрипт вставляет полученный html-код в страницу и обрабатывает результат.

Итак, одним из поставленных условий было то, что исходный код, предоставляемый smartresponder’ом не должен меняться. Поэтому первым делом средствами javascript и JQuery я меняю стандартную кнопку формы на собственную.

$(document).ready(function() {
	$('[name=SR_submitButton]').before('input name="SR_submitButton2" value=""');
	$('[name=SR_submitButton2]').attr('value', $('[name=SR_submitButton]').attr('value'));
	$('[name=SR_submitButton2]').attr('style', $('[name=SR_submitButton]').attr('style'));
	$('[name=SR_submitButton]').hide();
 
	$('[name=SR_submitButton2]').click(function(){
		My_submit();
	 });
});

SR_submitButton – имя кнопки формы, которую предоставляет smartresponder. Вместо неё мы создаем собственную кнопку с именем SR_submitButton2 (по ходу перенимая стиль и название исходной кнопки), а SR_submitButton скрываем (hide()). В качестве обработчика нажатия SR_submitButton2 вешаем собственную функцию My_submit(), код которой приведен ниже.

function My_submit(submit)
{
	var f = $('[name=SR_form]');
	if(submit && (SR_submit(f[0])))
	{
		$('[name=SR_form]').submit();
		return true;
	}else
	{
		if(!onSubscribeSubmit(f))
		{
			var iteration = $(f).data("iter");
			if(!iteration)
			{
				$(f).data("iter", 1);
			}else if(iteration > 2)
			{
				$(f).data("iter", 0);
				$('[name=SR_form]').submit();
			}else
			{
				$(f).data("iter", iteration + 1);
			}
		}
		return false;
	}
}

Кнопка SR_submitButton2 будет нажиматься до получения капчи и после ввода её пользователем. Соответственно в функции My_submit предусмотрено два варианта работы. Если в функцию был передан параметр submit равный true, то подразумевается, что с feedburner’ом мы уже разобрались, и данные готовы к отправке в smartresponder. Но перед этим вызовем стандартную smarteponder’овсую функцию SR_submit, которая проверит поля формы. Если проверка прошла успещно мы попадаем в тело оператора if и выполняется

$(’[name=SR_form]‘).submit(); – отправка данных в сервис smartresponder.

В противном случае данные передаются на обработку функции onSubscribeSubmit. Если последняя возвращает false – это говорит о безуспешной попытке ввода капчи.

К сожалению в FeedBurner наблюдается неприятный эффект с адресами, которые там уже зарегистрированы. При попытке подписки такого адреса, FeedBurner воспринимает это как ввод неверной капчи и присылает новую. В итоге получается что вне зависимости от ввода пользователя, ему присылается новая капча. Чтобы этого избежать я ограничил количество вводов капчи тремя попытками. Не получилось с трех попыток – делать нечего, переадресуем клиента на smartresponder, оставляя в покое FeedBurner. Иначе мы рискуем что клиент уйдет вообще ни с чем.

Теперь разберем функцию onSubscribeSubmit().

function onSubscribeSubmit(f)
{
	if(!(jQuery.data(document.body, 'email'))) 	// пытаемся прочесть ранее сохраненный параметр элемента body с именем email.
							// Его отсутствие говорит о том, что мы ещё не получали капчи от FeedBurner’а.
	{
		// берем из формы адрес подписчика и записываем как параметр body
		jQuery.data(document.body, 'email',  $('[name=field_email]').val());
		// здесь мы обращаемся к собственному серверному скрипту и передаем ему
		// в качестве параметра адрес электронной почты подписчика
		$.ajax({
		type: "GET",
		url: serverPath + 'fb_request.php?email=' + $('[name=field_email]').val(),
		dataType: "html",
		data: {},
		timeout: 30000,
			success: function(data) {
				// в случае успеха добавляем перед кнопкой текст с просьбой
				// ввести капчу и саму капчу, которую нам вернул скрипт
				$('.approvement').remove();
				$('[name=SR_submitButton2]').before('<div class="approvement"><span style="font-size: 10pt; font-family: Verdana;">'+
				prompt  + '</span>' +
				data + '</div>');
				return false;
			},
			error: function (XMLHttpRequest, textStatus, errorThrown) {
				return false;
			}
			});
	}else()
	{
		// если мы попали в эту ветку, значит мы обрабатываем нажатие
		// кнопки уже при введенной капче
		$('#fbresult').remove();     // удаляем старые результаты работы
		$.ajax({
		type: "GET",
		// все указанные ниже параметры, начинающиеся с fb присылаются при
		// предыдущем взаимодействии и вставляются в документ.
		// Теперь же их нужно передать обратно для корректного
		// взаимодействия с FeedBurner’ом
		url: serverPath + 'fb_request.php?email=' + $('#fbemail').attr('value') +
		'&token=' + $('#fbtoken').attr('value') +
		'&uri=' + $('#fburi').attr('value') +
		'&captcha=' + $('#fbcaptcha').attr('value') +
		'&action=' + $('#fbaction').attr('value') +
		'&cookie=' + $('#fbcookie').attr('value'),
		dataType: "html",
		data: {},
		timeout: 30000,
			success: function(data) {
				// вставляем полученный результат
				$('[name=SR_form]').before(data);
				// вызываем обработчик полученного результата
				return handleFbResult(f);
			},
			error: function (XMLHttpRequest, textStatus, errorThrown) {
				return false;
			}
		});
	}
}

Также надо объявить строку с предложением ввести капчу, которая используется в функции.

var prompt =  '<p style="font-size: 8pt;">' +
'Введите текст на картинке и повторно нажмите на кнопку Получить.</p>';

Ну а теперь осталось разобрать последнюю функцию – обработку результата отправления капчи. Наш серверный скрипт, получив ответ от FeedBurner’а, вернет кусок html кода, в котором будет элемент с. Нас интересует значение (value) этого элемента.

function handleFbResult(f)
{
	var result = $('#fbresult').attr('value');
	switch(result)
	{
		// в случае если подписка FeedBurner успешно активирована, либо
		// адрес уже находится в подписном листе
		case 'already':
		case 'success':
			$('.approvement').remove();
			My_submit(true);  // отправка в smartreponder
			return true;
		break;
		case 'repeat':
			// неправильный ввод – перезапрашиваем капчу
			jQuery.data(document.body, 'email', null);
			onSubscribeSubmit(f);
		return false;
		break;
		case 'invalid':
			// пользователь ввел некорректный email
			$(f).data("email", null);
			$(".approvement").html('<font style="font-size: 10pt; font-family: Verdana; color: red; font-weight: bold;">Некорректный email</font>');
			return false;
		break;
	}
}

Вот, собственно, и всё, что делает клиентский скрипт. В следующей статье рассмотрим серверную часть.

Автоматизация подписки на рассылку FeedBurner. Часть 1

February 12th, 2010 Alex Nikitin No comments

Проблема

Для начала объясню, зачем мне потребовалось автоматизировать подписку FeedBurner и почему это может быть полезно другим.

В интернете существует довольно много сервисов (в том числе бесплатных), которые позволяют одним делать email рассылки, а другим, на эти рассылки подписываться (smartresponder.ru, subscriber.ru, рассылки@mail.ru и т.д.). У каждого, конечно, есть свои достоинства и недостатки.

Так, например, есть сервис FeedBurner, от гугла. Он позволяет превратить rss-ленту блога в серию писем, что для многих очень удобно. Однако, кроме как отправлять новые посты блога, FeedBurner ничего не умеет.

В то же время помимо постов в блоге иногда возникает необходимость посылать дополнительные письма своим подписчикам (особенно для тех, у кого блог или сайт – инструмент ведения бизнеса). А для этого приходится использовать какой-либо другой сервис рассылки (например те, что были перечислены выше).

Как же быть с подписчиками? Просить подписаться на две рассылки сразу – вариант, прямо скажем, сомнительный: люди не любят оставлять свою электронную почту в интернете.
А почему бы не сделать следующим образом. Давайте напишем скрипт, который при оформлении подписки (не FeedBurner) будет сначала отправлять данные о e-mail в сервис FeedBurner и регистрироваться там, а уже потом переходить на стандартную для рассылки регистрацию.

Сказано – сделано, я написал скрипт для связи рассылки smartresponder.ru с FeedBurner. Поэтому весь дальнейший код, будет работать с формой, которая генерируется smartresponder’ом. Но, можно будет убедиться, что при небольших изменениях его можно будет использовать и с другими сервисами рассылок.

Теория

Проблема в том, что при подписке на рассылку в FeedBurner, последний генерит капчу для проверки на ботов. Эту капчу нужно получить и вывести пользователю, а потом его ввод передать обратно в сервис FeedBurner.

Ещё одно техническое условие: не вносить изменения в скрипт, который предоставляет smartresponder. Это условие я поставил для того, чтобы даже тот, кто ни в зуб ногой в веб-программировании, без проблем смог бы прикрутить этот дополнительный скрипт к уже имеющейся форме.

Архитектурно нам потребуется два модуля:

1. скрипт на Javascript, который:

  • после заполнения поля с e-mail передаст данные скрипту №2 и получит от него капчу FeedBurner’а.
  • после заполнения капчи передаст данные скрипту №2 и получит результат операции. В случае успеха перейдет на страницу регистрации в smartresponder’е.

2. скрипт на PHP, который:

  • Получает от первого скрипта данные с e-mail’ом, передает в FeedBurner. В ответ получает рисунок с капчей и дополнительные данные. После чего передает эту информацию обратно первому скрипту.
  • После ввода капчи передает её FeedBurner’у, получает результат и возвращает его первому скрипту.

Взаимодействие между первым и вторым скриптами будет происходить посредством AJAX. Иллюстрация:

1

2

По сути получается клиент-серверная архитектура. На стороне клиента – javascript, на сервере – php-скрипт, взаимодействующий с сервисов FeedBurner.

На этом пока всё. Во второй статье приведу код клиентской части. Там, кстати, я использовал JQuery. В третьей статье рассмотрим серверную сторону.