Отправляет email-рассылки с помощью сервиса Sendsay

Программирование в Web

  Все выпуски  

Программирование в Web Выпуск 13 от 21/05/2006 Основы PHP - Урок-8


 
Программирование в web

ОСНОВЫ PHP
 

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

 
У Р О К    8
Здравствуйте уважаемые подписчики!

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


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


НЕСКОЛЬКО «ХЛЕБНЫХ КРОШЕК» К ПРЕДЫДУЩИМ УРОКАМ:

Кое-что о цикле foreach:

Первая крошка. Помнится в Уроке 6 по основам PHP мы изучали циклы и там я сказал, что цикл foreach не допускает вложенности. На самом деле я немного покривил душой. Этот цикл вложенность тоже допускает, но при этом, вы должны быть уверены, что обрабатывается не одномерный массив, то есть, что вложенный цикл тоже будет работать с массивом. Таким образом вложенность циклов foreach всегда ограничена размерностью массивов, с которыми они работают. Это значит, что для обработки одномерного массива вы не сможете использовать вложенность этих специальных циклов, просто потому, что вложенный цикл будет ожидать массив, а получит простую переменную, PHP сгенерирует ошибку, которая будет выведена в браузер, а работа сценария прервется.



Кое-что о переменных:

Теперь немного вернусь к переменным. Это просто совет, старайтесь не давать переменным односимвольные имена, во-первых, в скором времени у вас может возникнуть нехватка этих самых переменных, а во-вторых, так проще запутаться. Кроме того, регистр символов в именах переменных играет большую роль, то есть $Ref и $ref, с точки зрения PHP - разные переменные.


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


<?php
$a=3;
if ($a) print "Переменная проинициализирована";
?>
Примечание: Здесь просто проверяем, имеет ли какое-то значение переменная $a и если имеет, проверка возвращает true.


или так:


<?php
$a=3;
if (!$b) print "Переменная не проинициализирована";
?>
Примечание: Здесь наоборот проверяем с отрицанием, то есть true будет возвращено в том случае, если переменная еще не существует (символ ! - символ логического НЕ).


и второй способ:


<?php
$a=3;
if (isset($a)) print "Переменная существует";
?>
Примечание: Здесь мы уже впрямую проверяем существование переменной (рекомендую использовать именно такой способ).


или так:


<?php
$a=3;
if (!isset($b)) print "Переменная не существует";
?>
Примечание: А здесь мы впрямую проверяем несуществование переменной (также рекомендую именно этот способ в случае проверки по отрицанию).


Однажды созданная переменная будет действовать на протяжении всей работы сценария (речь идет о глобальных переменных и объявленных внутри функций как статические). Но вы в процессе работы сценария можете самостоятельно уничтожить любую из переменных. Для этого надо всего лишь воспользоваться функцией unset(). Вот пример:


<?php
$a=3;
if ($a) print "Переменная проинициализирована<br>";
else print "Переменная не проинизиализирована<br>";
unset($a);
if ($a) print "Переменная проинициализирована";
else print "Переменная не проинизиализирована";
?>

Будет выведено две строки:
Переменная проинициализирована
Переменная не проинициализирована


То есть, сначала мы создали переменную со значением 3 и проверили ее существование, а затем уничтожили эту переменную и снова проверили ее существование.


В одном из первых уроков я рассказал о том, как можно проверить тип переменной, теперь же расскажу о том, как можно изменить этот тип. Для проверки типа переменной существует функция gettype() в которую в качестве параметра и передается имя переменной, чей тип проверяется. Для изменения типа переменной вручную (помните?, PHP автоматически задает тип переменных на основе хранящихся в них значений) используется функция settype($a, $type). Здесь в качестве параметров передается имя переменной и новый тип. Типы бывают, как я уже перечислял:


  • Integer - Целое число (например 1000);
  • Double - Число с плавающей точкой двойной точности (3,14);
  • String - Строка символов ("Крокодил");
  • Boolean - лигический тип (значения true, false, 1, 0 или даже пустая строка);
  • Object - Объект (при использовании объектно-ориентированного подхода);
  • Array - массив.

Изменить тип переменной можно функцией settype(), например вот так:
settype($var, integer)


Но эта функция сможет изменить тип переменной только, если значение сопоставимо с новым типом. Допустим, если в переменной $a находится число, то возможно изменение типа на строку или в тип Double. Но, если, например, в переменной хранится значение строка, не имеющая ничего общего с числом, то преобразовать эту переменную в тип Integer или Double попросту не удастся с помощью этой функции. Но функция все-равно сработает, просто в случае невозможности изменения типа переменной она вернет не переменную с новым типом, а значение false, то есть установку нового типа можно использовать в качестве условия проверки в конструкциях if или в циклах. Надо помнить и еще одну вещь, в качестве нового типа функция не устанавливает тип boolean, то есть этот тип даже не стоит и пробовать вставлять в функцию в качестве второго параметра.


Проверить конкретный тип переменной можно не только с помощью функции gettype(), но и с помощью других специализированных (такие проверки лучше всего применять в условиях циклов или конструкций if):


  • is_integer($a) - true если $a целое число;
  • is_double($a) - true если $a действительное число;
  • is_string($a) - true если $a строка;
  • is_numeric($a) - true если $a либо число, либо строка, содержащая и могущая быть представленной числом (рекомендуется использовать именно эту функцию вместо is_integer и is_double);
  • is_bool($a) - true если $a имеет одно из двух значение, true или false;
  • is_scalar($a) - true если $a имеет один из вышеперечисленных типов, то есть простых (скалярных);
  • is_null($a) - true если $a хранит значение NULL (пусто);
  • is_array($a) - true если $a является массивом;
  • is_object($a) - true если $a содержит ссылку на объект или (в PHP-4) является объектом.

Об операторе присваивания я вроде уже говорил, это одиночный символ равенство (=). Он сообщает PHP, что переменной или выражению, записанному с левой стороны от этого знака нужно присвоить (скопировать, передать, как хотите) значение выражения, расположенного справой стороны от этого знака (не забывайте про точку с запятой в конце каждого выражения). Не пытайтесь подменять оператор проверки равенства (== - два подряд идущих знаков равно) оператором присваивания. При проверке такого условия, скорее всего на выходе будет true, ведь присваивание должно по идее закончиться положительно. О ссылках (бывают символическими и жесткими) говорить много не буду, достаточно того, что было в одном из предыдущих уроков сказано. Применяются они достаточно редко, и особой выгоды от их использования получить не удается.



Константы:

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


Создаются константы (именно создаются) с помощью функции define($name, $value, $case_sensitive),
где $name - имя создаваемой константы, $value - ее значение (не допускается вычисление значения в момент создания, на момент создания значение должно быть уже вычислено), $case_sensitive - по умолчанию в true, то есть в дальнейшем в программе регистр букв имени константы будет учитываться, если поставить false, то учитываться регистр уже не будет. Но считается хорошим тоном писать имена констант именно с заглавных букв, дабы не путать их с именами функций или с именами переменных.


Проверить существование константы можно с помощью функции defined($constantname) - возвращает true, если константа с указанным именем была ранее определена.



Функции:

Имена функций, также как и имена переменных, являются регистрозависимыми. То есть имя ref и Ref будут разными.


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


Функцию print() мы уже знаем, она, и ее аналог (практически синоним) функция echo() выводят в окно браузера содержимое переданного им параметра (это может быть строка или конкретное значение, константа или значение переменной, вобщем эти функции занимаются выводом чего-то удобочитаемого в браузер. Обе они применяются очень часто, хотя не поддерживают форматирование вывода и из-за этого их практически нежелательно использовать для отладочных целей (например содержимое массива напрямую они вывести не способны, только отдельные значения или имена ключей). Но эти функции не единственные, например, есть функция print_r() (в отличии от print и echo, передаваемые ей параметры должны находиться в скобках всегда), которая способна вывести все содержимое массива в удобочитаемом виде, например, так:


Array
   (
      [a]=>apple
      [b]=>banana
      [c]=>Array
       (
         [0]=>1
         [1]=>2
         [3]=>3
       )
   )

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



ВЫРАЖЕНИЯ, ОПЕРАЦИИ, ОПЕРАТОРЫ:

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



СТРОКОВЫЕ ВЫРАЖЕНИЯ:

Строкой может быть все, что заключено в двойных кавычках или в апострофах. О различии этих двух представлений я уже рассказывал. Переменные внутри строки, ограниченной двойными кавычками интерполируются, то есть подставляют значения, также внутри таких кавычек можно получать и значения некоторых функций. Строки же, ограниченные апострофами, будут выведены на печать в браузер так, как есть, и некоторые специальные последовательности в них также не действуют. Специальные последовательности имеются ввиду такие:


  • \n - перевод строки (символ новой строки), рекомендуется использовать при необходимости сохранения данных в многострочном виде;
  • \r - символ возврата коретки;
  • \t - табуляция.

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


  • \$ - просто символ $, чтобы следующие за ним символы не воспринимались как имя переменной;
  • \" - просто кавычка как просто символ;
  • \\ - просто обратный слэш и ничего больше (\);
  • \xNN - обозначает символ с шестнадцатеричным кодом NN.

ОПЕРАТОРЫ И ОПЕРАЦИИ:

  • . - точка - предназначена для конкатенации (слияния) строк;
  • + - плюс;
  • - - минус;
  • * - умножить;
  • / - деление;
  • % - остаток от деления (деление по модулю);
  • = - равно - присвоение правого значения выражения левому;
  • == - два знака равно подряд - проверка равенства без учета типов (то есть строка "3" будет в результате проверки равна числу 3, 0, чаще всего, может быть равен false, любое другое значение, отличное от нуля, а также единица, могут быть равны true, это следует учитывать при проверки различных условий, например, while(0) тело цикла никогда не выполнится, так как ноль в данном случае может быть проинтерпретирован как false, а while(1) - бесконечный цикл, я его уже описывал);
  • != - восклицательный знак и равно - проверка на неравенство без учета типа значений;
  • < - меньше;
  • > - больше;
  • <= - меньше либо равно;
  • >= - больше либо равно;
  • === - три равно подряд - проверка эквивалентности, то есть проверка равенства, но с учетом и типов значений (рекомендуется вместо двух равно подряд);
  • !== - восклицательный знак и два знака равно подряд - проверка неэквивалентности - то есть с учетом и типов;
  • ++ - инкремент значения;
  • -- - два минуса подряд - декремент.

Если операторы инкремента и декремента используются в операции присваивания ($b=$a++ или $b=++$a), то играет роль место их записи, если они ставятся после инкрементируемого/декрементируемого значения, то сначала выполняется операция присваивания и только потом вычисляется инкремент, но если эти операторы записаны до инкрементируемого/декрементируемого значения, то сначала вычисляется это значение, а затем уже производится операция присваивания. Это надо учитывать.


  • += - плюс и равно подряд - увеличение значения на значение из правой части;
  • -= - минус и равно подряд - уменьшение на значение из правой части;
  • .= - к строке слева присоединить строку справа;
  • *= - звездочка и равно подряд - увеличение в ... раз;
  • /= - слэш и равно подряд - уменьшение в ... раз;
  • %= - процент и равно - остаток от деления на значение.

Эти три последние применяются очень редко.


Битовые операции:


  • a&b - по логическому И - результат число, у которого установлены по позициям биты и а и b одновременно - операция объединения практически;
  • a|b - число у которого установлены биты либо по позициям а либо по позициям b;
  • ~a - инвертирование битов (где были нули - единицы и наоборот);
  • a<<b - поразрядный сдвиг на b битов влево;
  • a>>b - поразрядный сдвиг на b битов вправо.

Битовые операции в Web применяются очень редко, но приятно сознавать, что они все же есть.


Логические операторы:


  • ! - НЕ - true если false и false если true;
  • and - И - true если оба операнда true;
  • && - И - true если оба операнда true, отличается от предыдущего более высоким приоритетом при проверки в выражении, то есть && выше, чем and и выполнится раньше and;
  • or - ИЛИ - true если true хотя бы один из операндов или оба сразу;
  • || - ИЛИ - true если true хотя бы один из операндов или оба сразу, отличается от or более высоким приоритетом, то есть также как && от and;
  • xor - исключающее ИЛИ - true если true только один из операндов.

Порядок вычисления операторов такой:


  • ++, --
  • /, *, %
  • +, -
  • <, <=, >=, >
  • ==, ===, !=
  • &&
  • ||
  • =, +=, -=, /=, *=, %=, .=
  • and
  • xor
  • or

&&, ||, ! помимо других приоритетов имеют еще один, сравнение производится строго слева направо. То есть, если одна из проверок завершится false, то остальная часть выражения даже не будет проверяться. Вот пример:
$variable=0&&(time()>100);
Учитывая, что в логическом контексте ноль трактуется как false, в вышеуказанном выражении функция time() никогда не будет выполнена, так как уже возвращено false. И еще, не забывайте про удвоение символов, например | и || абсолютно разные операторы, первый потенциально может возвращать любое число, а второй только false или true.


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



ВКЛЮЧАЮЩИЕ ИНСТРУКЦИИ:

Кто пользовался хоть раз SSI, знают приятные тамошние инструкции, с помощью которых в один документ можно включить текст из другого файла (#include virtual и #include file). Приятно сознавать, что в PHP тоже существуют чем-то похожие инструкции (за исключением, что не позволяют делать виртуального включения). С помощью этих инструкций можно подключить к документу не только текстовый документ, но и другой сценарий, который будет также неплохо выполняться. Чаще всего эти инструкции применяют когда надо подключить какой-то файл с настройками, с библиотеками функций или классов, а также при построении шаблонов сайтов по блочной или иной архитектуре. Вот как это примерно могло бы выглядеть: Допустим у нас имеется некий сайт и нам надо внести в его оформление (например в шапке) несколько изменений. Для статичного html нам пришлось бы последовательно вносить одинаковые изменения во множество файл ов. Но можно поступить по другому. Разбить шаблон страницы на несколько блоков (шапка, меню слева, основное поле, поле справа, подвал и так далее) и затем последовательно включать эти блоки (естественно в порядке их следования в структуре html) с файлы страниц (хоть конечно в этом случае тоже не получается движка сайта, но хотя бы при этом можно максимально абстрагироваться от оформления страниц при изменении контента). Конечно, можно сделать целиком шаблонную структуру сайта, то есть где-то в одном (или по желанию, что проще, в двух) файлах будет храниться шаблон оформления сайта и по необходимости будет в нужных местах подключаться. Ну об это позже. Сейчас же о самих инструкциях:


include("$filename");


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


require("$filename");


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


include_once("$filename"); и
require_once("$filename");


Сначала проверяют, не был ли подключен ранее файл с именем "$filename" и если уже подключен и функционирует, то функция ничего не делает. Если же файл с указанным адресом и именем еще не был подключен, то они его подключают точно так же, как и include() и require() с теми же самыми отличиями. Инструкции (или точнее функции include_once("$filename") и require_once("$filename"); удобно применять при подключении файлов библиотек функций или библиотек классов. Так как, если файл библиотеки функций был уже подключен и подключается вновь (возможно из разных сценариев, но функции транслируются глобально, поэтому будут продолжать быть доступны) то любой вызов какой-нибудь функции, ранее определенной в этом включаемом файле вызовет ошибку, что мол функция объявлена дважды и работа сценария прервется. Так что имейте это ввиду, когда будете подключать библиотеки.


Кто до сих пор пользуется чистым html, с помощью инструкций включения файлов могут легко и ощутимо облегчить себе жизнь и заботу по обновлению контента сайта и его дизайна. Ведь используя хотя бы только эти инструкции вы сможете максимально абстрагироваться от оформления страниц при изменении контента, или от контента при изменении дизайна, кроме того, размеры страниц станут намного меньше, ведь в них будет содержаться минимум html, да и то только тот, который необходим для частного оформления различных абзацев, форм, таблиц. Советую подумать об этом и переключиться на эти инструкции. Уверяю, прелесть не замедлит проявиться. Есть еще один плюс. Дизайн сайта считается хорошим, если он выглядит единообразно, используя подход с включениями файлов, вы сможете в едином ключе изменять все страницы изменив всего лишь навсего один (или несколько, если шаблон разбит на блоки и надо их все изменить) файл. Согласитесь, э то намного проще и удобнее. Таким образом ваш сайт уже можно будет считать почти движковым. Почти. Как сделать простейший движок - расскажу в другой раз, возможно когда изучим еще некоторые функции и тонкости. А пока лишь намекну, как, например, мог бы выглядеть простейший движок. Допустим это файл index.php:


<?php
require_once("какой-нибудь файл с настройками или функциями");
include ("файл шаблона шапки страниц");
include ("файл меню сайта");
include ("файл контента страниц"); // Здесь конечно перед этим
// включением должны быть какие-нибудь проверки и условия, на
// основании которых будет выбираться какую-именно страницу с каким
// контентом подключить, допустим это условие будет выбираться исходя
// из того, какой пункт меню был нажат посетителем.
include ("файл шаблона подвала страниц");
?>

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


И последняя «крошка» касается некоторых прелестей PHP. Как уже успели заметить, PHP, чуть ли не единственный язык программирования, который настолько услужлив, что все ошибки выводит в окно браузера, с указанием файла и строки в этом файле, где эта ошибка произошла, то есть вам осталось открыть нужный файл в нужном месте и исправить ошибку. Удобно. На самом деле этот вывод ошибок настраивается в конфигурации PHP, в файле php.ini. Чаще всего на хостингах это и включено по умолчанию, так что ошибки будут выводиться (правда в разном объеме, потому, что имеется четыре типа ошибок по их критичности). Ну а коли они выводятся в окно браузера, это не всегда бывает то, что нужно. Часто, при отладке, зная, где может потенциально возникнуть ошибка для временного отключения сообщения об ошибках можно применять специальный подход. Заключается этот подход в том, что в проверяемом условии, где может возник нуть ошибка, перед функцией или выражением ставится символ лягушки (или собаки, кому как удобнее называть), то есть символ @. Например:
if (@file_exists("$filename")) {что-то делаем}
Стандартная функция file_exists(), забегая вперед скажу, проверяет существование файла, путь и имя которого передано ей в параметрах. На самом деле в данном случае такое временное отключение сообщений об ошибке не нужно, функция file_exists() возвращает корректное значение, в случае, если файл не существует. Но это может потребоваться при открытии файлов, там возвращаемое значение не всегда может оказаться таким, которое нам нужно, да и ошибку генерирует если она возникает. Но об этом позже.


 

 
Объявления!!!

Если у вас возникли какие-то вопросы, не стесняйтесь, задавайте. Для этого всего лишь пошлите на мой адрес письмо с вопросом и с темой, в которой обязательно укажите какого урока и какого языка касается вопрос. Например, тема может быть такой - «Основы PHP. Урок 8. Вопрос». Также, с такой же темой и на этот же адрес вы можете прислать и свои дополнения к уроку. Если дополнения будут существенными, то в последующих уроках они обязательно будут учтены.

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

Маленькая просьба. Многие из вас присылают мне запросы на отправку им того или иного, не полученного ранее, выпуска рассылки. У меня не всегда есть возможность это сделать. Поэтому предлагаю вам для начала посмотреть нужный выпуск в архиве рассылки. Рассылка выходит сразу на трех рассыльных сервисах, и по некоторым причинам (мне не известным) некоторые сервисы не принимают адресов, в которых встречаются именя конкурирующих рассыльных сервисов, по этой причине я опубликую только одну ссылку на архив рассылки только одного рассыльного сервиса, а именно Content.mail.ru. Вот ссылка на на этот архив рассылки - http://content.mail.ru/pages/p_21931.html. В обще м-то это объявление наверное лишнее, ведь в каждом выпуске каждой рассылки, вне зависимости от того, на каком сервисе она выходит, всегда указан этот самый адрес архива рассылки. На сабскрайб.ру и на контент.мэйл.ру этот адрес указывается в самом низу выпуска, а на мэйллист.ру - в самом верху выпуска. Так что, при достаточном внимании, вы сами легко сможете найти нужную ссылку на архив рассылки.

 

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

 
Наш проект
Автор рассылки — Anatolick
Архив рассылки — http://content.mail.ru/pages/p_21931.html
Сайт проекта — «Russian discussions Zone»
Движок для сайтов — «Tanat-Engine»
Дискуссионный лист — «Все для Всех о РС»
Дискуссионный лист — «File Info Masters»
Дискуссионный лист — «Напряги мозги»
Дискуссионный лист — «Ищем все…»
Дискуссионный лист — «Все обо Всем по Email»
Дискуссионный лист — «Внимание! Розыск…»
Дискуссионный лист — «Английский для Всех и каждого»
Группа стандартизации в Web — W3C.org
 

В избранное