SNK Software
Web Studio Монополия Metaproducts Утилиты Игры
Монополию Web Studio Библиотека
Вебмастер Дельфи Работа на ПК Самоучитель
Для PHP Для Delphi
Веб-дизайн Программирование Компьютеры Девайсы Заметки
SNK Software Индустрия hardware Индустрия software
О студии Портфолио Сопровождение сайтов

Новые материалы

Девайсы:
Сравнительный обзор Nokia Lumia 920 и HTC 8X
Девайсы:
Обзор Nokia Lumia 820 – смартфона на WP8
Вебмастеру:
Настройка Apache, PHP и MySQL для Linux-VPS
Вебмастеру:
VPS на домашнем ПК: настройка сети в VM VirtualBox и Debian
Вебмастеру:
VPS на домашнем ПК: устанавливаем Linux Debian 6
Вебмастеру:
VPS на домашнем ПК: установка VM VirtualBox
Работа на компьютере:
Иные возможности текстового процессора Word
Работа на компьютере:
Вставка объектов
Работа на компьютере:
Таблицы в Word
Работа на компьютере:
Печать и сохранение документов
Работа на компьютере:
Сноски, колонтитулы, оглавление и указатели в Word

Строки и регулярные выражения JavaScript

В этой главе:

  1. Объект String
  2. Объект RegExp

В предыдущей главе мы ознакомившись с основными объектами языка JavaScript – Object, Array и другими. Теперь рассмотрим еще пару объектов — String и RegExp. Первый предназначен для работы со строками, а второй — с регулярными выражениями, которые, в свою очередь, применяются над текстом — т.е. теми же строками.

Объект String

Если Number – объектная оболочка для числовых значений, то для строковых предусмотрен объект-оболочка String. Фактически, String представляет собой объект, содержащий последовательность символов. Но при этом следует различать объекты типа String, и строковые последовательности литералов:

str1 = "Строка!" // str1 получит значение типа строкового литерала str2 = new String("Строка!") // str2 получит значение типа объект String

Несмотря на эти различия, вы можете совершенно свободно применять методы объекта String, и пользоваться его методом “length” при работе с «обычными» строками, заданными литералами, поскольку интерпретатор языка JavaScript по необходимости самостоятельно создает из литералов временные объекты. Тем не менее, различия могут проявиться, например, при выполнении некоторых операций над строками:

str1 = "2 + 2" // создаем строковой литерал str2 = new String("2 + 2") // создаем объект String

Казалось бы, значения переменных str1 и str2 должны быть одинаковыми. В этом можно убедиться, выведя значения обеих в документ:

document.writeln(str1); document.writeln(str2);

В обоих случая будет выведено значение «2+2». А теперь давайте оценим результат при помощи метода eval:

document.writeln(eval(str1)); document.writeln(eval(str2));

Теперь в первом случае мы получим число 4, поскольку механизм автоматического преобразования JavaScript, оценив выражение 2+2, решит, что это арифметическое выражение и выдаст его результат. Во втором же случае мы по-прежнему получим исходное выражение (2+2), поскольку ранее было явно сказано, что str2 – это именно строка, а потому никаких преобразований не требуется.

ПРИМЕЧАНИЕ
Если вы уже разобрались, чем отличается объект от литерала, то можете взять на заметку, что существует еще и предопределенная функция String, служащая для того, чтобы конвертировать значение любого объекта в строку. Она была описана ранее, в разделе, посвященном функциям языка JavaScript.

Разобравшись с отличиями, перейдем к свойствам и методам объекта String. Со свойствами все обстоит довольно просто: кроме constructor и prototype, объект String имеет всего одно собственное свойство – length, указывающее на длину строки в символах. Например, следующее выражение вернет в качестве ответа 7:

length("Привет!");

Вместе с тем, для объекта String предусмотрен весьма обширный набор методов, предназначенных для произведения различных манипуляций над строками. Все они перечислены в таблице 4.14.

Таблица 4.14. Методы объекта String
Метод и его синтаксисОписание
anchor(Имя)Создает якорь (назначение) для HTML-ссылок. Имя указывается как значение атрибута NAME.
big()Указывает, что строка должна быть выведена более крупным шрифтом (добавляет )
blink()Указывает, что строка должна быть выведена мерцающим шрифтом (добавляет ), кроме MSIE
bold()Указывает, что строка должна быть выведена полужирным шрифтом (добавляет )
charAt(индекс)Возвращает указанный символ из строки, где индекс – номер символа, начиная с нуля
charCodeAt (индекс)Возвращает указанный Unicode-символ из строки, где индекс – номер символа, начиная с нуля
concat(строка1, ..., строкаN)Склеивает две или более строки, и возвращает новую строку
fixed()Указывает, что строка должна быть выведена моноширинным шрифтом (добавляет )
fontcolor(цвет)Указывает, что строка должна быть выведена определенным цветом (добавляет )
fontsize(размер)Указывает, что строка должна быть выведена определенным размером кегля (добавляет )
fromCharCode (num1,...,numN)Возвращает строку, созданную из указанной последовательности Unicode-символов
indexOf(искомое [,НачальныйИндекс])Возвращает индекс первого совпавшего символа в вызывающей строке, и -1, если искомое не найдено. Начальный индекс задает позицию, с которой начинается поиск по строке
italics()Указывает, что строка должна быть выведена наклонным шрифтом (добавляет )
lastIndexOf(искомое[,НачальныйИндекс])Аналогичен методу indexOf, но возвращает позицию последнего символа
link(URL)Создает гиперссылку
match(РегВыр)Применяет регулярное выражение к строке
replace(РегВыр, строка)Применяет регулярное выражение к строке, и заменяет совпавшие значения на указанную в параметре строку
search(РегВыр)Выполняет поиск на соответствие между строкой и регулярным выражением. Возвращает позицию регулярного выражения в строке, или -1, если не найдено
slice(НачальныйИндекс[, КонечныйИндекс])Выделяет часть строки с указанной позиции до конца, или до второй указанной позиции, и возвращает новую строку
small()Указывает, что строка должна быть выведена более мелким шрифтом (добавляет )
split (разделитель[, максимум])Разбивает строку на массив, ориентируясь на указанный разделитель. Второй параметр может указывать на максимальное число элементов
strike()Указывает, что строка должна быть выведена зачеркнутой (добавляет )
sub()Указывает, что строка должна быть выведена по нижней оси (добавляет )
substr(НачальныйИндекс[, КонечныйИндекс])Возвращает подстроку из строки, начиная со стартового значения, и до конца, либо до второй указанной позиции
substring(индекс1, индекс2)Возвращает подмножество объекта String
sup()Указывает, что строка должна быть выведена по верхней оси (добавляет )
toLowerCase()Преобразует символы строки в нижний регистр
toUpperCase()Преобразует символы строки в верхний регистр

Очевидно, что большая часть методов этого объекта, появившаяся еще во времена Netscape 2.0, и предназначенная для вывода форматированного текста, на сегодня попросту устарела. Особенно это важно учитывать в свете того, что многие теги и атрибуты являются неодобряемыми в HTML 4 Transitional или попросту отсутствуют в Strict-версии или в XHTML. В связи с этим, следует избегать любых методов, работающих с HTML-оформлением. В частности, к таковым относятся методы blink(), strike(), fontsize() и fontcolor(). Вместе с тем, не желательно использовать и любые другие методы, создающие HTML-код: например, метод ancor() использует для создания метки элемент A с атрибутом NAME, в то время как при помощи атрибута ID можно привязать метку к любому элементу. Более того, в XHTML 1.1 для элемента A вообще не предусмотрено атрибута NAME.

Перейдем теперь к более прогрессивным методам String. Начнем с преобразования регистра символов, для чего мы имеем 2 метода – toLowerCase и toUpperCase. Для этого вновь воспользуемся функцией вывода выражение: результат, только на этот раз дадим ей более универсальное имя, скажем, PrintWithEval:

function PrintWithEval (val) { document.write(val + ": " + eval(val) + "<br />"); }

Теперь присвоим переменной s строковое значение, после чего выведем результаты применения методов toLowerCase и toUpperCase:

var s = "Город Москва"; document.open(); PrintWithEval("s.toLowerCase()"); PrintWithEval("s.toUpperCase()"); Document.close();

В первом случае результатом будет строка, состоящая только из строчных букв («город москва»), а во втором – только из прописных («ГОРОД МОСКВА»).

Теперь кратко рассмотрим способы применения остальных методов объекта String. Например, чтобы получить единичный символ, содержащийся в определенном месте строки, используют метод charAt:

s.charAt(6);

В случае если s содержит строку «Город Москва», будет возвращен символ «М» (т.е. седьмой, поскольку отсчет ведется с 0). А если нам нужен не символ, а его код в таблице символов Unicode, то следовало бы воспользоваться методом charCodeAt:

s.charCodeAt(6);

В таком случае возвращаемым значением будет число 1052 – Unicode-код прописной буквы «М».

Теперь рассмотрим ситуацию, когда из строки необходимо «извлечь» не единичный символ, а несколько, скажем, слово. В таком случае можно воспользоваться методом substr, возвращающим строку, содержащую часть исходной строки:

s.substr(6,3);

Для исследуемой нами строки возвращаемым значением будет строка, начинающаяся с 7-го символа исходной строки и длиной 3 символа, т.е. «Мос». Если бы второй параметр был опущен, то возвращаемым значением было бы «Москва», т.е. вся строка с 7-го символа и до конца.

Несколько по-другому работает метод substring. В отличие от substr, он возвращает фрагмент строки между двумя указанными индексами. Причем за начальный индекс всегда берется меньший из двух указанных аргументов:

s.substring(5,2); s.substring(2,5);

Оба данных выражения дадут один и тот же результат – «род », т.е. символы с 3-го по 5-й включительно. Метод substring можно использовать для написания собственной функции замены. При этом, помимо собственно метода substring от строк нам потребуется только свойство length:

function replaceString(oldS, newS, fullS) { for (var i=0; i<fullS.length; i++) { if (fullS.substring(i, i+oldS.length) == oldS) { fullS = fullS.substring(0,i) + newS + fullS.substring(i+oldS.length,fullS.length); } } return fullS; }

Данная функция производит поиск по строке путем сдвига на 1 символ вправо при каждой итерации цикла, и в случае успеха совершает замену найденного слова на новое, а в качестве результата возвращает новую строку. Например, если в качестве строки для замены использовать «Hello, World!», то чтобы заменить «World» на «Web», следует вызвать ее со следующими параметрами:

var rez = replaceString("World", "Web", "Hello, World!");

В данном случае возвращаемым значением будет «Hello, Web!». Причем если бы исходная строка содержала не 1, а несколько слов «World», то заменены были бы все нашедшиеся совпадения.

Итак, у нас имеется собственная функция, при помощи которой можно производить замену в строках. Но у объекта String имеются собственные методы, которые позволяют производить, по крайней мере, поиск. Это – indexOf и lastIndexOf. Первая, как следует из описания, возвращает порядковый номер первого совпавшего символа в строке, а вторая – последнего. Например, для того, чтобы найти первый или последний символ «о» во фразе «Город Москва», достаточно написать:

s.indexOf('о'); s.lastIndexOf('о');

Результатом в первом случае будет 1 (не забываем, что отсчет начинается с 0), а во втором – 7.

Два других метода – concat и split используются для того, чтобы склеивать и разделять строки. Использование первого метода состоит в том, что указанные в качестве параметров строки объединяются в одну, вместе с исходной, а полученный результат возвращается в качестве новой строки:

str1 = "Город"; str2 = " "; str3 = "Москва"; str4 = str1.concat(str2, str3); // Получим строку "Город Москва"

Что касается метода split, то он выполняет обратную операцию – разделяет одну строку на несколько, ориентируясь на указанный разделитель:

str1 = "Город Москва"; array1 = str1.split(" "); //Получим массив из 2 элементов ["Город","Москва"]

Если же указать второй параметр, то количество элементов в возвращаемом массиве будет ограничено, при этом будут внесены только первые совпадения, а оставшаяся часть строки – проигнорирована. Следует отметить, что в качестве значения разделителя можно указать не только строку, но и регулярное выражение:

array1 = str1.split(/\s*,\s*/);

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

Объект RegExp

Объект RegExp служит для работы с регулярными выражениями. Поддержка регулярных выражений была впервые предложена Netscape при разработке языка JavaScript версии 1.2, предназначенного для Netscape Communicator 4.0, а в версии 1.5 она получила дальнейшее развитие. В Opera 6 и MSIE 5 также имеется поддержка регулярных выражений, однако не в полной мере, а у MSIE, помимо этого, имеются некоторые отличия в реализации некоторых свойств и методов. Наконец, MSIE 6 и Opera 7, как и Mozilla, имеют полную поддержку регулярных выражений в соответствии с описываемым стандартом.

Регулярные выражения являются мощнейшим инструментом для работы со строками, но изучение всех тонкостей работы с ними – как вообще, так и в реализации JavaScript, выходит за рамки данной книги, здесь же приводятся только наиболее общие сведения.

Итак, для создания объекта типа RegExp используется следующий синтаксис:

new RegExp("образец"[, "флаги"])

Здесь «образец» – собственно текст регулярного выражения, а необязательный параметр «флаги» может принимать одно из следующих значений, или их сочетаний в любой последовательности:

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

re = new RegExp("ab+c","i") // регулярное выражение, созданное конструктором re = /ab+c/i // такое же регулярное выражение, но заданное литералом

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

MyRegExp = new RegExp("\\w+") // перед ключом \w добавлен слеш MyRegExp = /\w+/ // выражение записано как есть

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

Ознакомившись с правилами синтаксиса, принятыми в JavaScript для регулярных выражений, но прежде, чем перейти к свойствам и методам объекта RegExp, рассмотрим синтаксис самих регулярных выражений. Итак, в строке регулярного выражения используется ряд специальных символов, которые сами по себе или в сочетании с другими образуют условия выражения. Полный перечень спецсимволов, поддерживаемых в JavaScript, приведен в таблице 4.15.

Таблица 4.15. Специальные символы в регулярных выражениях
СимволОписание
\Символ «обратный слеш» указывает на то что если следующий символ – обычная буква то он является спецсимволом регулярного выражения (например \b – спецсимвол) в противном случае – наоборот обратный слеш означает что следующий спецсимвол следует воспринимать как обычную букву (например \* будет воспринят как *)
^Совпадение в начале строки. Для примера, /^М/ соответствует «Москва» но неверно в случае «Город Москва»
$Совпадение в конце строки. Например, /$ш/ соответствует «Кукиш» но неверно для «Кукушка»
*Совпадение предшествующего символа 0 или более раз. Например, /ше*/ соответствует для «наш» и «наше»
+Совпадение предшествующего символа 1 или более раз. Так, /ше+/ соответствует для «наше» и «длинношеее» но неверно для «наш»
?Совпадение предшествующего символа 0 или 1 раз. Например, /ше?/ соответствует для «наше» и «наш» но неверно для «длинношеее»
.Совпадение с любым предшествующим символом кроме символа новой строки. Например, /.а/ совпадет в строке «а не сходить ли в баню?» с буквой «а» в слове «баня» но не с первой буквой в строке
(x)Ищет совпадение с «x» и запоминает найденное. К примеру, (дом) совпадет в строке «наш дом – Россия» и будет сохранен в массив результатов выполнения
(?:x)Ищет совпадение с «x» но не запоминает найденное. Например, (дом) совпадет в строке «наш дом – Россия» но не будет сохранен в результатах
x(?=y)Совпадает с «x» только если «x» следует непосредственно за «y». Так, /А(?=Б/ совпадет с «А» в «АБ» но не в «А Б»
x(?!y)Совпадает с «x» только если «x» не следует непосредственно за «y». Например /А(?!Б/ совпадет с «А» в «А Б» но не в «АБ»
x|yСовпадение с «x» или с «y». Так, (белый|черный) совпадет со словом «белый» в строке «белый мерин» и со словом «черный» в строке «черный бумер»
(n)Совпадение символа строго указанное (n) число раз подряд в предшествующих символах. Так, /е(2)/ совпадет с «зеленее» и с первыми двумя «е» в «длинношеее» но неверно для «наше»
(n)Совпадение символа указанное (n) либо большее число раз подряд в предшествующих символах. Например, /е(2)/ совпадет с «зеленее» и со всеми тремя «е» в «длинношеее»
(x,y)Совпадение не меньше чем x и не более чем y раз. К примеру, /o(1,3)/ совпадет с одним «o» в «home» и с двумя – «google» а также с первыми тремя в «Goooogle»
[абв]Совпадение с любым из перечисленных символов. Допускается указать диапазон букв через дефис например [а-я]
[^абв]Несовпадение с любым из перечисленных символов. Например, [^а-г] верно для «жук», поскольку в этом слове отсутствуют символы а б в г
[\b]Совпадение с символом забоя (backspace)
\bСовпадение с границей слова (например с пробелом)
\BСовпадение с не-границей слова. Например /[а-я]\B\о/ совпадет только с последней буквой «о» в «око»
\cXСоответствует символу управления. Например /\cC/ соответствует Ctrl+C
\dСовпадение с любой цифрой. Эквивалентно [0-9]
\DСовпадение с любым символом кроме цифры. Эквивалентно [^0-9]
\fСоответствует символу «конец страницы»
\nСоответствует символу «конец строки»
\rСоответствует символу «возврат каретки» (return)
\sСоответствует любому единичному пробельному (разделительному) символу. Эквивалентно [\f\n\r\t\u00A0\u2028\u2029]
\SСоответствует любому символу кроме разделительного т.е. аналогично записи [^ \f\n\r\t\u00A0\u2028\u2029]
\tСоответствует табулятору
\vСоответствует вертикальному табулятору
\wСоответствует любому буквенно-цифровому символу
\WСоответствует любому не буквенно-цифровому символу
\0Соответствует NUL
\числоОбратная ссылка на последнее совпадение подстроки указанное число раз назад в данном выражении
\xhhСоответствует символу с кодом hh (ASCII)
\uhhhhСоответствует символу с кодом hhhh (Unicode)

Разумеется, можно задавать регулярные выражения без использования спецсимволов, однако при этом функции работы с ними ограничатся банальным поиском. Например, если в качестве шаблона выражения указать «/а/», то он совпадет в строке «Москва» (последний символ), но не совпадет в строке «Питер», поскольку в ней нет символа «а».

Так или иначе, но теперь, когда мы знакомы с синтаксисом и со спецсимволами, перейдем к свойствам и методам объекта RegExp, чтобы рассмотреть, как все это работает. Свойства объекта RegExp включают в себя constructor, prototype, а так же ряд иных свойств, перечисленных в таблице 4.16.

Таблица 4.16. Свойства объекта RegExp
СвойствоОписание
globalУказывает, используется или нет флаг «g» с данным выражением
ignoreCaseУказывает, используется или нет флаг «i» с данным выражением
lastIndexУказывает на индекс в строке, с которого следует начинать поиск следующего соответствия
multilineУказывает на то, состоят ли входные данные из нескольких строк
sourceУказывает на текст данного регулярного выражения

Следует учитывать, что все приведенные в таблице 4.16 свойства в JavaScript 1.5 относятся не к самому объекту RegExp, а непосредственно к его экземплярам. В то же время в JavaScript 1.3 эти свойства являются статическими, т.е. не относятся к конкретному объекту типа RegExp. Кроме того, в ранних версиях JavaScript свойств было больше, но теперь они объявлены устаревшими, хотя учитывая обратную совместимость JavaScript, на практике все равно они будет работать, по крайней мере, в реально существующих браузерах, поэтому рассмотрим и их тоже:

ПРИМЕЧАНИЕ
Если вы знакомы с языком Perl, то можете заметить, что короткий синтаксис свойств регулярных выражений (со знаком $) в JavaScript аналогичен применяемому в Perl. Это не удивительно, учитывая, что именно на основе работы регулярных выражений в Perl и разрабатывалась их поддержка в JavaScript.

Ознакомившись со всеми свойствами, рассмотрим методы объекта RegExp. В JavaScript 1.5 предусмотрены методы exec и test, которые позволяют оценить выражение (test) или выполнить поиск (exec). Метод exec «запускает» регулярное выражение, т.е. выполняет поиск совпадений:

MyRegExpr.exec([str])

Он может быть вызван и неявно:

MyRegExpr([str])

Если в качестве параметра не указать строки, то будет передано содержимое свойства input. Это же верно и для метода test:

MyRegExpr.test([str])

Разница между этими двумя методами состоит в том, что exec возвращает массив строк с совпавшими значениями (и изменяет свойства глобального объекта RegExp), а test – только возвращает true или false, в зависимости от того, найдено ли было хоть одно совпадение. Помимо этого, регулярное выражение может быть оценено при помощи методов объекта String, в частности – match, replace или search:

var MyRegExp = new RegExp("[0-9]+ февраля"); str2 = "С праздником 23 февраля!"; newstr2=str1.replace(MyRegExp, "8 марта"); str1 = "С праздником 7 февраля!"; newstr1=str1.replace(MyRegExp, "8 марта");

В результате выполнения метода replace в обоих случаях мы получим строку «С праздником 8 марта!». Более интересный пример использования регулярного выражения – это проверка, скажем, адреса электронной почты на допустимость. Рассмотрим это подробнее, для чего обратимся к листингу 4.6.

Листинг 4.6. Метод test

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"> <html> <head> <title>Проверка адреса почты</title> </head> <body> <script type="text/JavaScript"><!-- function CheckMail(str) { re = /[_a-z\d\-\.]+@[_a-z\d\-]+(\.[_a-z\d\-]+)/i if (re.test(str)) alert("Все верно!"); else alert("Не подходит!"); } //--></script> <form> <input type="Text" id="email" value="" /> <input type="Button" value="Проверка!" onclick="CheckMail(email.value);" /> </form> </body> </html>

Прежде всего, здесь определена функция CheckMail, которая будет проверять строку, введенную в текстовое поле формы. Эта функция вызывается при помощи атрибута ONCLICK, указанного для обычной кнопки формы, причем ей передается в качестве параметра содержимое текстового поля, находящегося в этой же форме. Для этого использовано имя идентификатора этого текстового поля – «email» со свойством «value». Такое выражение является одним из примеров использования объектной модели документа, что мы, собственно говоря, будем подробно рассматривать в следующей главе. А пока перейдем к самой функции. В ней, прежде всего, определен шаблон регулярного выражения, при помощи которого можно (в целом) проверить, введен ли адрес электронной почты корректно.

Корректным адресом, удовлетворяющим требованию данного регулярного выражения, может быть как достаточно простой email, вроде «name@domain.com», так и достаточно специфический, но допустимый, тем не менее, по правилам синтаксиса адресов электронной почты, например, «user51.all-users@firm_2.com.net.ru». В то же время адрес, не содержащий «собаки» (@) или точки в имени домена, принят не будет.

Собственно проверку осуществляет метод test, возвращающий «ложь» или «истину» в зависимости от того, совпадет с регулярным выражением принятая функцией строка, или нет.

Для проверки корректности URL можно использовать примерно такое регулярное выражение:

/([ftp]|[http]):\/\/([_a-z\d\-]+(\.[_a-z\d\-]+))((\/[ _a-z\d\-\\\.]+)+)*/i

Пример использования этого и некоторых других часто применяемых регулярных выражений (например, номеров телефонов) можно посмотреть в файле regexp.html.

2011-08-02 // Есть вопросы, предложения, замечания? Вы можете обсудить это на форуме !

Избранное

SNK GSCP
SNK GSCP - новая библиотека для PHP 5!
Web Studio
Web Studio и Visual Workshop
Библиотека:
Стандарты на web-технологии
Монополия
Монополия Android
Загрузки:
скачать программы
Продукция:
программы и книги
Техподдержка / Связаться с нами
Copyright © 1999-2020 SNK. Все права защищены.
При использовании материалов с сайта ссылка на источник обязательна.
Рейтинг@Mail.ru