Основные функции на примере парсера XML

Всем привет! Сегодня я хочу на примере парсера (программы, которая будет извлекать контент с удаленного сайта и выводить на нашей странице) погоды из xml-файла с сайта gismeteo.ru, описать некоторые основные функции в PHP. Так сказать, чтобы было наглядно. Выбрал я xml потому что парсить его гораздо легче, нежели HTML, да и после очередной смены дизайна сайта гизметео, нужно было бы каждый раз переписывать код. Я уже имел опыт написания парсера на C#, поэтому написать парсер на PHP было не так сложно.

На сайте gismeteo.ru можно получить xml для любого города. Я выбрал Санкт-Петербург, потому что живу в нем=)). Прогноз выдается здесь на сутки. Итак выбираем город, тут же получаем ссылку на xml: http://informer.gismeteo.ru/xml/27612_1.xml.

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

<MMWEATHER>
<REPORT>
<TOWN index="27612" sname="%CC%EE%F1%EA%E2%E0" latitude="55" longitude="37">
<FORECAST day="07" month="01" year="2011" hour="15" tod="2" predict="0" weekday="6">
<PHENOMENA cloudiness="0" precipitation="10" rpower="0" spower="0"/>
<PRESSURE max="760" min="758"/>
<TEMPERATURE max="-9" min="-7"/>
<WIND min="4" max="6" direction="3"/>
<RELWET max="99" min="97"/>
<HEAT min="-14" max="-12"/>
</FORECAST>
<FORECAST day="07" month="01" year="2011" hour="21" tod="3" predict="0" weekday="6">
<PHENOMENA cloudiness="0" precipitation="10" rpower="0" spower="0"/>
<PRESSURE max="758" min="756"/>
<TEMPERATURE max="-10" min="-8"/>
<WIND min="5" max="7" direction="3"/>
<RELWET max="99" min="97"/>
<HEAT min="-16" max="-14"/>
</FORECAST>
<FORECAST day="08" month="01" year="2011" hour="03" tod="0" predict="0" weekday="7">
<PHENOMENA cloudiness="2" precipitation="10" rpower="0" spower="0"/>
<PRESSURE max="757" min="755"/>
<TEMPERATURE max="-7" min="-5"/>
<WIND min="5" max="7" direction="4"/>
<RELWET max="99" min="97"/>
<HEAT min="-11" max="-9"/>
</FORECAST>
<FORECAST day="08" month="01" year="2011" hour="09" tod="1" predict="0" weekday="7">
<PHENOMENA cloudiness="3" precipitation="6" rpower="0" spower="0"/>
<PRESSURE max="757" min="755"/>
<TEMPERATURE max="-4" min="-2"/>
<WIND min="4" max="6" direction="4"/>
<RELWET max="100" min="98"/>
<HEAT min="-7" max="-5"/>
</FORECAST>
</TOWN>
</REPORT>
</MMWEATHER>

Ниже есть описание формата, которое я тоже скопирую с сайта гизметео:

TOWN информация о пункте прогнозирования:
Index уникальный пятизначный код города
Sname закодированное название города
Latitude широта в целых градусах
Longitude долгота в целых градусах
FORECAST информация о сроке прогнозирования:
day, month, year дата, на которую составлен прогноз в данном блоке
hour местное время, на которое составлен прогноз
tod время суток, для которого составлен прогноз: 0 — ночь 1 — утро, 2 — день, 3 — вечер
weekday день недели, 1 — воскресенье, 2 — понедельник, и т.д.
predict заблаговременность прогноза в часах
PHENOMENA атмосферные явления:
cloudiness облачность по градациям: 0 — ясно, 1- малооблачно, 2 — облачно, 3 — пасмурно
precipitation тип осадков: 4 — дождь, 5 — ливень, 6,7 – снег, 8 — гроза, 9 — нет данных, 10 — без осадков
rpower интенсивность осадков, если они есть. 0 — возможен дождь/снег, 1 — дождь/снег
spower вероятность грозы, если прогнозируется: 0 — возможна гроза, 1 — гроза
PRESSURE атмосферное давление, в мм.рт.ст.
TEMPERATURE температура воздуха, в градусах Цельсия
WIND приземный ветер
min, max минимальное и максимальное значения средней скорости ветра, без порывов
direction направление ветра в румбах, 0 — северный, 1 — северо-восточный, и т.д.
RELWET относительная влажность воздуха, в %
HEAT комфорт — температура воздуха по ощущению одетого по сезону человека, выходящего на улицу
Ну и вот собственно сам код парсера:

<?
//Выводим на экран сообщение
echo "<strong>Погода в Санкт-Петербурге</strong> <hr/></br>";
//Получаем содержимое
$content = file_get_contents("http://informer.gismeteo.ru/xml/27612_1.xml");
//выделяем прогноз на определенное время суток
preg_match_all("#<forecast(.*?)</forecast>#is", $content, $forecast);
//цикл для каждого прогноза
foreach($forecast[1] as $weather) {
//считываем информацию о дате и времени прогноза с помощью регулярных выражений
preg_match("#day=\"(.*?)\" month=\"(.*?)\" year=\"(.*?)\" hour=\"(.*?)\" tod=\"(.*?)\" predict=\"(.*?)\" weekday=\"(.*?)\">#is", $weather, $srok);//информация о сроке прогнозирования
//присваиваем значение дня
$day = $srok[1];
//присваиваем значение месяца
$month = $srok[2];
//присваем значение года
$year = $srok[3];
//выбор варианта времени суток
switch($srok[4])
{
case 3:
$daytime = 'ночь';
break;
case 9:
$daytime = 'утро';
break;
case 15:
$daytime = 'день';
break;
case 21:
$daytime = 'вечер';
break;
default:
$daytime = '';
break;
}
//выбор варианта дня недели
switch($srok[7])
{
case 1:
$weekday = 'Воскресение';
break;
case 2:
$weekday = 'Понедельник';
break;
case 3:
$weekday = 'Вторник';
break;
case 4:
$weekday = 'Среда';
break;
case 5:
$weekday = 'Четверг';
break;
case 6:
$weekday = 'Пятница';
break;
case 7:
$weekday = 'Суббота';
break;
default:
$weekday = '';
break;
}
//вывод даты, дня недели и времени суток на экран
print "<strong>Дата:</strong> $day.$month.$year, $weekday.  <strong>Время суток:</strong> $daytime. </br>";

//считываем информацию об атмосферных явлениях с помощью регулярных выражений
preg_match("#<PHENOMENA cloudiness=\"(.*?)\" precipitation=\"(.*?)\" rpower=\"(.*?)\" spower=\"(.*?)\"/>#is", $weather, $phenomena);//атмосферные явления
//выбор варианта облачности
switch($phenomena[1])
{
case 0:
$cloudness = 'ясно';
break;
case 1:
$cloudness = 'малооблачно';
break;
case 2:
$cloudness = 'облачно';
break;
case 3:
$cloudness = 'пасмурно';
break;
default:
$cloudness = '';
break;
}
//выбор варианта осадков
switch($phenomena[2])
{
case 4:
$precipitation = 'дождь';
break;
case 5:
$precipitation = 'ливень';
break;
case 6:
$precipitation = 'снег';
break;
case 7:
$precipitation = 'снег';
break;
case 8:
$precipitation = 'гроза';
break;
case 9:
$precipitation = 'нет данных';
break;
case 10:
$precipitation = 'без осадков';
break;
default:
$precipitation = '';
break;
}
//вывод информации об облачности и осадках на экран
print "<strong>Облачность:</strong> $cloudness.  <strong>Осадки:</strong> $precipitation. </br>";

//считываем информацию о давлении с помощью регулярных выражений
preg_match("#<PRESSURE max=\"(.*?)\" min=\"(.*?)\"/>#is", $weather, $pressure);//давление
//вывод информации о давлении на экран
print "<strong>Давление:</strong> $pressure[1]..$pressure[2] мм.рт.ст. </br>";

//считываем информацию о температуре с помощью регулярных выражений
preg_match("#<TEMPERATURE max=\"(.*?)\" min=\"(.*?)\"/>#is", $weather, $temp);//температура
//вывод информации о температуре на экран
print "<strong>Температура:</strong> $temp[1]°..$temp[2]°C. </br>";

//считываем информацию о ветре с помощью регулярных выражений
preg_match("#<WIND min=\"(.*?)\" max=\"(.*?)\" direction=\"(.*?)\"/>#is", $weather, $wind);//приземный ветер
//вывод информации о ветре на экран
print "<strong>Ветер:</strong> $wind[1]..$wind[2] м/с. </br>";

//считываем информацию о влажности воздуха с помощью регулярных выражений
preg_match("#<RELWET max=\"(.*?)\" min=\"(.*?)\"/>#is", $weather, $relwet);//относительная влажность воздуха %
//вывод информации о влажности воздуха на экран
print "<strong>Влажность воздуха:</strong> $relwet[1]%..$relwet[2]%. </br>";

//рисуем горизонтальную линию (ну это уже из HTML...)
print "<hr/>";
}
//заканчиваем выполнение скрипта
?>

Результат выполнения скрипта можно увидеть здесь.

А теперь подробнее обо всем что там написано.

Ну начало кода PHP (<?) мы помним из прошлого поста, так же как конец и комментарии (?> и // соответственно). Перейдем к следующей строке:

echo "<strong>Погода в Санкт-Петербурге</strong> <hr/></br>";

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

<?=”Hello World”?>

Но сокращенный синтаксис допустим только когда включена директива конфигурации short_open_tag.

Помимо echo() в своем коде я использовал print(), который тоже служит для вывода информации. Но в отличии от echo(), print() является функцией, которая всегда возвращает значение. Вследствие чего echo() даже выполняется быстрее, но не на много.

Пойдем дальше. Тут следует немного рассказать про переменные в PHP.

Переменные в PHP состоят из знака $ и идентификатора. Идентификатор – это имя переменной. (Имена функций и классов так же являются идентификаторами, но их пока не трогаем.) Идентификаторы подчиняются некоторым простым правилам:

Идентификаторы могут иметь любую длину и состоять из букв, цифр, символов подчеркивания и знаков доллара. Однако при использовании в идентификаторах знаков доллара следует проявлять внимательность, но об этом позже.
Идентификаторы не могут начинаться с цифры.
В PHP идентификаторы чувствительны к регистру. $test и $Test – разные идентификаторы.
Идентификаторы переменных могут совпадать с именами встроенных функций, однако это вызывает путаницу и этого следует избегать.
Переменные в PHP не обязательно объявлять, прежде чем использовать. Переменная будет создана при первом присвоении ей значения. Тип переменной зависит от хранящихся в ней данных.

PHP поддерживает следующие типы данных:

Integer (целый) – Используется для целых чисел
Double (двойной точности) – Используется для действительных чисел
String (строковый) – Используется для строк символов
Array (массив) – Используется для хранения нескольких элементов данных одного типа (подробно о них позже)
Object (объект) – Используется для хранения экземпляров классов (подробно о них позже)
Итак далее у нас по коду следует:

$content = file_get_contents(«http://informer.gismeteo.ru/xml/27612_1.xml»);
Здесь мы видим функцию file_get_contents() которая получает содержимое файла в виде одной строки. Данная функция идентична функции file().Использование функции file_get_contents() наиболее предпочтительно в случае необходимости получить содержимое файла целиком, поскольку для улучшения производительности функция использует алгоритм ‘memory mapping’ (если поддерживается операционной системой). В качестве имени файла для данной функции можно использовать URL, что мы и делаем.

Значит здесь у нас создается переменная $content и в нее записывается содержимое нашего xml-файла в виде одной строки.

Далее у нас идет строка

preg_match_all(«#<forecast(.*?)</forecast>#is», $content, $forecast);
в которой мы выделяем с помощью регулярных выражений, содержимое блоков FORECAST.

Регулярные выражения (англ. regular expressions) — это формальный язык поиска и осуществления манипуляций с подстроками в тексте, основанный на использовании метасимволов. По сути это строка-образец (англ. pattern, по-русски её часто называют «шаблоном», «маской»), состоящая из символов и метасимволов и задающая правило поиска.

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

Основа регулярного выражения – шаблон. С его помощью мы описываем формат нужного нам фрагмента текста, а затем либо проверяем, подходит ли текст под шаблон, либо вырезаем одно или несколько вхождений шаблона, либо заменяем на какой-либо текст.

Последовательность символов строке можно найти с помощью квантификаторов. Вспоминаем английский: quantity – количество. Т.е. квантификатор – то, что выражает количество чего-то, в нашем случае количество символов в условии поиска.

Основными функциями по работе с регулярными выражениями в PHP являются preg_match(), preg_match_all() и preg_replace().

preg_match() – выполняет подстановку регулярного выражения.
preg_match_all() – выполняет глобальный поиск совпадения регулярного выражения.
preg_replace() – выполняет поиск и замену регулярного выражения.
В своем примере я использовал функции preg_match_all() и preg_match().

Описание функции preg_match_all():

int preg_match_all (string pattern, string subject, array matches [, int flags])
int указывает на то, что функция возвращает значение типа Integer – это количество найденных совпадений, в скобках указаны следующие параметры:

string pattern – шаблон по которому происходит поиск (он же патэрн), тип – строковый
string subject – это строка в которой мы будем искать, тип – строковый
array matches – это массив совпадений который он найдет, тип – массив строк
int flags – установка флагов (параметр не обязательный) рассматривать пока не будем.
Описание функции preg_match():

int preg_match (string pattern, string subject [, array matches [, int flags]])
Эта функция будет искать в строке (string subject) совпадения с шаблоном заданным в pattern. Здесь в отличии от preg_match_all() массив совпадений (matches) является параметром не обязательным, т.е. если его нет, то функция вернет лишь количество совпадений шаблона. Это будет либо 0 раз (нет совпадений), либо 1 раз, поскольку preg_match() остановит поиск после первого найденного совпадения. preg_match_all(), наоборот, продолжит поиск до достижения конца строки(subject). preg_match() возвращает FALSE при возникновении ошибки.

Теперь немного о шаблоне.

В шаблонах можно использовать следующие ключи:

g — заменить все одинаковые компоненты, а не один, как в отсутствии ключа g.
i — не учитывать регистр.
m — строка, в которой происходит поиск, состоит из множества строк.
s — строка, в которой происходит поиск, состоит из одной строки.
x — сложный шаблон, т.е. можно писать не в строчку, а для упрощения понимания разбивать шаблон на несколько строк, примеры об этом ниже.
o — компилировать шаблон один раз.
U — Инвертирует «жадность» для каждого квантификатора (если же после квантификатора стоит «?», этот квантификатор перестает быть «жадным»).
A — привязка к началу текста.
E — заставляет символ «$» совпадать только с концом текста. Игнорируется, если установлен парамерт m.
e — Строка замены интерпретитуется как PHP код.
Для того чтобы ключи не сливались с шаблоном, его содержимое помещается между знаками #.

Например: “#пример(.*?)шаблона#is”

В скобках указываются квантификаторы. Квантификатор (.*?) означает что будут найдены любые символы, которые повторяются 0 и более раз.

Т.е. в нашем примере

preg_match_all(«#<forecast(.*?)</forecast>#is», $content, $forecast);
мы берем все что у нас находится между <forecast и </forecast> и записываем это в массив символов $forecast.

Далее по коду у нас идет цикл:

foreach($forecast[1] as $weather) {блок}
Кострукция foreach представляет собой разновидность цикла for, включенную в язык для упрощения перебора элементов массива. Существует две разновидности команды foreach, предназначенных для разных типов массива:

foreach ($массив as $элемент) { блок }
foreach ($массив as $ключ => $элемент) { блок }
При выполнении следующего фрагмента:

$a=array(«X»,»Y»,»Z»)
foreach ($a as $b) { echo «$b<br>»; }
Будет выведен результат:

X
Y
Z

Второй вариант используется при работе с ассоциативными массивами (о которых позже):

$a=array{
«X»=10,
«Y»=20,
«Z»=30 }
foreach ($a as $b=>$c) {
echo «$a=>$c
«;}
Результат:
X=>10
X=>20
X=>30

Таким образом на одном проходе цикла, в переменной $weather у нас будет находится все то, что заключено между <forecast и </forecast>, с чем дальше и работаем в цикле. Где мы уже по аналогии с preg_match_all(), с помощью preg_match() сохраняем в соответствующие массивы ($srok, $phenomena, $pressure, $temp, $wind, $relwet) по несколько штук значения различных параметров погоды, а так же даты и времени суток. Но т.к. время суток, день недели и некоторые другие параметры в xml у нас обозначены цифрами, расшифровка которых приведена в описании формата, то мы используем оператор выбора (он же переключатель) switch для сопоставления этим цифрам нормальных русских обозначений.

Переключатель switch является наиболее удобным средством для организации мультиветвления. Синтаксис переключателя таков:

switch(expression) // переключающее выражение
{
case value1: // константное выражение 1
statements; // блок операторов
break;
case value2: // константное выражение 2
statements;
break;
default:
statements;
}

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

Ну а далее мы просто выводим на экран все полученные нами значения с помощью функции print(), где в кавычках у нас заключены переменные с подписями и HTML-тэгами.

Кстати записи

print "<strong>Облачность:</strong> $cloudness.  <strong>Осадки:</strong> $precipitation. </br>";

и

print ("<strong>Облачность:</strong> %s.  <strong>Осадки:</strong> %s. </br>", $cloudness, $precipitation);

идентичны и будут выведены одинаково. Просто во втором примере на место каждой последующей %s вставляется каждая последующая переменная, следующая после кавычек.

Описывать каждую функцию preg_match(), switch() и print() я не стал, потому что они все идентичны!

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