Сергей Кушниров

Проверка валидности xml на PHP

Автор: Сергей Кушниров
01.09.2010

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

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

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

Давайте рассмотрим пример php-кода и принцип его работы.

Перед началом работы в папку со скриптом необходимо поместить .htaccess файл, в котором прописываем:

php_flag magic_quotes_gpc Off

Этот код обеспечивает работу «контейнера» который мы создадим с помощью нашей программы. В него мы будет помещать код для проверки. После того, как .htaccess файл готов, создаем еще один файл index.php, в котором и будет находиться наш php-обработчик.

Открываем файл index.php и пишем в него следущий код (код с комментариями) :

<?php//GET CONTENTif(isset($_POST[’text’])) $text = $_POST[’text’];

Суперглобальный массив $_POST применяется для того, чтобы в переменную $text внести значение, которое мы вписали в наш «контейнер», и передали в файл index.php:

function translitIt($str){$tr = array("А"=>"A","Б"=>"B","В"=>"V","Г"=>"G","Д"=>"D","Е"=>"E","Ж"=>"J","З"=>"Z","И"=>"I",		"І"=>"I","Ї"=>"I","Й"=>"Y","К"=>"K","Л"=>"L","М"=>"M",		"Н"=>"N","О"=>"O","П"=>"P","Р"=>"R","С"=>"S","Т"=>"T","У"=>"U","Ф"=>"F","Х"=>"H","Ц"=>"TS","Ч"=>"CH","Ш"=>"SH","Щ"=>"SCH","Ъ"=>"","Ы"=>"YI","Ь"=>"","Э"=>"E","Є"=>"E","Ю"=>"YU","Я"=>"YA","а"=>"a","б"=>"b","в"=>"v","г"=>"g","д"=>"d","е"=>"e","є"=>"e","ж"=>"j","з"=>"z","и"=>"i","і"=>"i","й"=>"y","к"=>"k","л"=>"l","м"=>"m","н"=>"n","о"=>"o","п"=>"p","р"=>"r","с"=>"s","т"=>"t","у"=>"u","ф"=>"f","х"=>"h","ц"=>"ts","ч"=>"ch","ш"=>"sh","щ"=>"sch","ъ"=>"y","ы"=>"yi",		"ї"=>"i","’"=>"y","ь"=>"","э"=>"e","ю"=>"yu","я"=>"ya");return strtr($str,$tr);}

Функция «translitIt()» нужна для того, чтобы весь русскоязычный текст, который мы вводим в «контейнер» и передаем на обработчик, преобразовался в транслит (translite), потому что функция, которая преобразовывает значение переменной xml формата в массив, не поддерживает русский язык.

$text_en = translitIt($text); //конвертируем значение переменной в транслит$xml_content = @simplexml_load_string($text_en);

Функция «simplexml_load_string ()» превращает значение переменной $text_en в массив, при условии, что значение переменой полностью соответствует формату xml:

if($xml_content) {$answ[] = «Это XML, все отлично!»;$yes = 1;

Идет проверка. Если функция смогла переконвертировать переменную в массив, то ее значение (переменной) полностью соответствует всем требованиям формата xml, и мы присваиваем массиву $answ положительный ответ. Тогда программа заканчивает свое выполнение и выводит «Это XML, все отлично!». Если же функция не смогла переконвертировать значение в массив - значит, что-то не соответствует стандарту. Либо xml написан с ошибкой, тогда программа пропускает данный пункт и выполняется дальше:

} else {//IF NOT XML//TAKE OPEN TAG$content = $text_en;do {preg_match(’/<\w+\s*(\s*\w*="[a-zA-Z0-9]*\s*)*\s*>/’,$content,$tag);$content = strstr($content,$tag[0]);$content = substr($content,strlen($tag[0]));preg_match(’/<(\w*)\s*/’,$tag[0],$name_tag);$open_tag[] = $name_tag[1];} while (preg_match(’/<\w+\s*(\w*=".*")*\s*>/’,$content,$tag));

Данный отрывок кода сохраняет в массив $open_tag названия всех открытых тегов в нашем коде. Принцип его работы прост – функция preg_match по заданному шаблону находит тот отрывок текста, который имеет строение открывающихся тегов. Если программа «увидела» такой код, она его запоминает, после чего берет основной код, убирает из него найденный отрывок и продолжает искать дальше. Этот поиск продолжается до тех пор, пока программа больше не сможет найти код, который имеет структуру открывающегося тега. После этого имена всех найденных тегов заносятся в массив $open_tag.

//TAKE CLOSE TAG$content = $text_en;do {preg_match(’/<\/\w*>/’,$content,$tag);$content = strstr($content,$tag[0]);$content = substr($content,strlen($tag[0]));preg_match(’/<\/(\w+)>/’,$tag[0],$name_tag);$close_tag[] = $name_tag[1];} while (preg_match(’/<\/\w*\s*>/’,$content,$tag));

Дальше берется изначальный проверяемый код, и весь цикл повторяется снова, только теперь для закрывающихся тегов. После чего имена всех найденных тегов заносятся в массив $close_tag:

//MAKE ARRAY WITH NUMBER OF THE SAME NAMES$b_open = array();foreach($open_tag as $tag) {	if(!in_array($tag,$b_open))

{$b_open[] = $tag; $c_open[$tag] = 1;} else {$c_open[$tag] = $c_open[$tag]+1;}; };

Этот отрывок программы отвечает за то, чтобы cформировать один массив $b_open, в котором содержатся все имена открытых тегов, с условием, что эти имена не повторяются.

Если встречается название, которое уже есть в массиве (с помощью функции in_array), то в массив $c_open прибавляется «1» к тому значению, где уже содержится это имя. По умолчанию у всех значений выставлено значение «1» - это означает, что тег существует и встречается только 1 раз. C каждой последующей его встречей к этой единичке прибавляется еще одна, и таким образом происходит подсчет всех значений.

В результате, мы имеем на выходе два массива: первый $b_open, который содержит в себе все уникальные названия найденных тегов в нашем документе, и второй массив $c_open, который содержит в себе информацию о том, сколько раз повторяется тот или иной тег. Данный отрывок кода работает со всеми открывающимися тегами.

//MAKE ARRAY WITH NUMBER OF THE SAME NAMES$b_close= array();foreach($close_tag as $tag) {	if(!in_array($tag,$b_close))	{$b_close[] = $tag; $c_close[$tag] = 1;}	else	{$c_close[$tag] = $c_close[$tag]+1;};	};

Аналогичный код, только уже для закрывающихся тегов.

А сейчас - самое интересное. Мы будем определять, что конкретно не так с кодом, который мы проверяем. Принцип проверки основан на том, что мы берем каждый тег, и смотрим, сколько раз он встречается как открывающийся, и сколько - как закрывающийся. В правильно оформленном xml документе на каждый открывающийся тег должен быть аналогичный закрывающийся. Либо может быть всего один тег, он же является открывающимся и закрывающимся тегом одновременно, поэтому его мы не учитываем.

//CONDITIONS OF SHOWING WARNINGSforeach ($b_open as $tag) {	$diff = $c_open[$tag]-$c_close[$tag] ;

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

if($diff != 0) {$num = $c_open[$tag]-$c_close[$tag];$ready_tag_open = htmlspecialchars("<$tag>");$ready_tag_close = htmlspecialchars("</$tag>");

Функции htmlspecialchars () используются для того, чтобы все вводимые знаки, которые каким-то образом могут повлиять на работоспособность нашей странички, кодировались в специальные, «безобидные» символы.

А теперь сами условия:

if($num > 0 && isset($c_close[$tag])) $answ[] = "Не закрывается либо отсуствует тег \"$ready_tag_close\"";

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

if($num < 0 ) $answ[] = "Не открывается либо отсутствует тег \"$ready_tag_open\"";

Аналогично и с закрывающимися тегами:

if($c_open[$tag] > 0 && $c_close[$tag] == 0) $answ[] = "Не закрывается либо отсутствует тег \"$ready_tag_close\"";if($c_close[$tag] > 0 && $c_open[$tag] == 0) $answ[] = "Не открывается либо отсутствует тег \"$ready_tag_open\"";}}}

Последние два условия у нас проверяют теги, которые встречаются в документе всего один раз. Условие можно перевести так – если открывающийся тег встречается больше от «0» и выше раз, но при этом ни разу не встречается аналогичный закрывающийся тег – это ошибка. Аналогично второе условие, только уже для закрывающихся тегов

Следующий код выводит на страничку все полученные значение в виде формочки:

//SHOW FORMecho "



if(empty($answ) && $text) {$answ_ = "Структура XML нарушена!"; echo "

";.$answ_."

";}


Если мы чего-либо не предусмотрели, и при этом .xml файл читается некорректно, выводим сообщение «структура xml нарушена». Если мы смогли определить, в чем конкретно ошибка, то тогда начинает работать следующий отрывок кода, который циклично выводит все замечания по неправильной структуре xml.

if($answ) {foreach ($answ as $answ) {if($yes == 1) {echo "

".$answ."

";}

Если мы ввели правильный .xml, подключается стиль «result_yes» для вывода нашего результата (ответ зеленого цвета):

else {echo "

".$answ."

";

А если имеются ошибки в xml коде, то к выводу этих ошибок подключается другой стиль «result_no», и выводится информация в красном цвете:

};};};echo "</form>";

Работу этого скрипта можно увидеть в начале данной статьи.



 Вернуться назад  | Вернуться в блог

Комментарии:

Добавить комментарий