PHP 2016. Уровень 3.3: PHP + XML
Работа с xml-документами. конспект-памятка видеокурса 'Программирование на PHP 2016'. Лектор Борисов Игорь Олегович. Учебный Центр «Специалист» при МГТУ им. Н.Э.Баумана.
конспектировал Капустин Яков
оглавление
- 01 Введение в XML
- 02 Правила XML
- 03 Корректность и валидность XML-документов
- 04 Средства работы с xml
- 05 Simple API for XML (SAX)
- 06 Парсинг xml-документа с помощью SAX
- 07 DOM (Document Object Model)
- 08 Парсинг xml-документа с помощью DOM
- 09 Парсер xml и html-сущности
- 10 Лабораторная работа №3.1 Создание RSS с помощью DOM
- 11 SimpleXML
- 12 Парсинг xml-документа с помощью SimpleXML
- 13 Лабораторная работа 3.3.2 Чтение RSS с помощью SimpleXML
- 14 Преобразование XML. XSL/T
01Введение в XML
XML (/ˌeks em ˈel/ англ. eXtensible Markup Language) — расширяемый язык разметки. Рекомендован Консорциумом Всемирной паутины (W3C). Спецификация XML описывает XML-документы и частично описывает поведение XML-процессоров (программ, читающих XML-документы и обеспечивающих доступ к их содержимому). XML разрабатывался как язык с простым формальным синтаксисом, удобный для создания и обработки документов программами и одновременно удобный для чтения и создания документов человеком, с подчёркиванием нацеленности на использование в Интернете.
XML является подмножеством SGML.
Язык называется расширяемым, поскольку он не фиксирует разметку, используемую в документах: разработчик волен создать разметку в соответствии с потребностями к конкретной области, будучи ограниченным лишь синтаксическими правилами языка. Расширение XML — это конкретная грамматика, созданная на базе XML и представленная словарём тегов и их атрибутов, а также набором правил, определяющих какие атрибуты и элементы могут входить в состав других элементов. Сочетание простого формального синтаксиса, удобства для человека, расширяемости, а также базирование на кодировках Юникод для представления содержания документов привело к широкому использованию как собственно XML, так и множества производных специализированных языков на базе XML в самых разнообразных программных средствах.
XML это набор синтаксических правил. Каждый раз, когда мы пишем свой xml-файл, мы фактически создаём свой уникальный язык разметки. Если предположить, что HTML это автомобиль, то XML это станок для производства автомобилей.
Предназначен для:
- хранения структурированных данных;
- обмена информацией между программами;
- создания на его основе других, более специализированных, языков разметки (OFX, OTP, WSDL, SOAP, VML, XSL, ebXML, CML, XLANG).
Цель создания: обеспечение совместимости при передаче структурированных данных между разными системами обработки информации.
Различия XML и HTML:
- HTML описывает ИЗ ЧЕГО СОСТОИТ и КАК отображать документ;
- XML определяет ЗНАЧЕНИЕ и ОТНОШЕНИЕ данных.
02Правила XML
- Если документ содержит символы, выходящие за рамки ASCII, необходимо указать кодировку;
- XML-документ состоит из вложенных элементов;
- Элемент состоит из открывающего и закрывающего тегов, а также содержимого;
- XML чувствителен к регистру символов;
- Элементы могут вкладывться друг в друга;
- Теги должны быть правильно вложены друг в друга;
- Все парные теги должны быть закрыты;
- Возможно формирование пустых элементов;
- Должен существовать только один корневой элемент, который содержит все остальные элементы. Пустой документ (без корневого элемента) недопустим;
- Элементы могут иметь атрибуты;
- Значения атрибутов заключаются в одинарные или двойные кавычки;
- У каждого конкретного элемента не должно быть повторяющихся атрибутов.
Создадим пример xml-файла:
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<catalog><!-- корневой элемент -->
<book>
<author>Виктор Олегович Пелевин</author>
<title>Омон Ра</title>
<pubYear>1992</pubYear>
<price>200</price>
<curr>RUB</curr>
</book>
<book>
<author>Чарльз Майкл (Чак) Палагнюк</author>
<title>Бойцовский клуб</title>
<pubYear>1997</pubYear>
<price>300</price>
<curr>RUB</curr>
</book>
<book>
<author>Виктор Олегович Пелевин</author>
<title>Смотритель</title>
<pubYear>2015</pubYear>
<price>300</price>
<curr>RUB</curr>
</book>
<book>
<author>Клим Александрович Жуков</author>
<title>Почему не надо казнить чучело Солженицына</title>
<pubYear>2016</pubYear>
<price>50</price>
<curr>RUB</curr>
</book>
<book>
<author>Виктор Олегович Пелевин</author>
<title>Достающее звено. 2 тома</title>
<pubYear>2017</pubYear>
<price>600</price>
<curr>RUB</curr>
</book>
<book>
<author>Виктор Олегович Пелевин</author>
<title>Тайные виды на гору Фудзи</title>
<pubYear>2018</pubYear>
<price>300</price>
<curr>RUB</curr>
</book>
</catalog>
Выведем созданный файл во фрейме для проверки на отсутствие ошибок:
2
3
echo '<iframe src="https://yakoffka.ru/src/conspects/_php33/_classes/example_fb.xml" width="100%" height="60"></iframe>';
03Корректность и валидность XML-документов
Корректными XML-документами (well-formed) являются документы, полностью соответствующие правилам оформления XML. Корректность проверяется XML-парсером. Валидные XML-документы (valid) - корректные XML-документы, которые соответствуют заранее определенному набору правил. Валидность проверяется валидатором.
Описание структуры документа возможно через DTD – Document Type Definition, либо через XML схемы.
04Средства работы с xml
В курсе php необходимо научиться читать (парсить), изменять и создавать xml. Для этого в php существуют следующие средства работы с xml:
- SAX (Simple API for XML) - чтение XML-документа;
- DOM (Document Object Model) - чтение, модификация и создание новых XML-документов;
- SimpleXML - чтение и модификация XML-документов;
- XMLReader и XMLWriter - чтение и модификация XML-документов;
- XSL/T (Extensible Stylesheet Language Transformations) - преобразование XML-документов в другие форматы
05Simple API for XML (SAX)
(англ. Простой программный интерфейс для работы с XML, сокращенно SAX ) - спецификация прикладного программного интерфейса для последовательного получения данных из структурированных XML документов. Этот интерфейс является распространенной альтернативой Document Object Model (DOM) но, в отличие от DOM, для SAX нет формальной спецификации.
Синтаксический анализатор SAX только сообщает о каждом событии синтаксического анализа в том порядке, в котором они происходят, и обычно отбрасывает почти всю эту информацию после сообщения (однако он сохраняет некоторые данные, например, порядок и список всех элементов, которые еще не были закрыты, чтобы потом отлавливать ошибки, такие как незакрытые элементы и неправильный порядок их закрытия)
Таким образом, минимальный объем памяти, необходимый для синтаксического анализатора SAX, пропорционален максимальной глубине XML-файла (Дерева XML) и объему данных, участвующих в одном событии XML (например, имени и атрибутах текущего элемента). Объём занимаемой памяти обычно считается незначительным по сравнению с парсером DOM, который напротив для начала должен построить древовидное представление всего документа в памяти, используя память таким образом, что она увеличивается по мере чтения документа (это занимает значительное время). Преимущество DOM заключается в том, что после загрузки любая часть документа может быть доступна в любом порядке.
Анализатор, реализующий интерфейс SAX (англ. SAX Parser ) обрабатывает информацию из XML документа как единый поток данных. Этот поток данных, как было указано выше, доступен только в одном направлении, то есть, раньше обработанные данные невозможно повторно прочитать без повторного анализа. Парсеры SAX работают с каждым фрагментом XML-документа последовательно, генерируя события синтаксического анализа.
SAX анализаторы реализуют с использованием подхода передачи сообщений (event-driven), когда программисту необходимо описать обработчики событий (en: Callback (computer science)), которые вызываются анализаторами при обработке XML документа.
SAX был разработан усилиями сообщества без формальных комитетов, но он был быстро признан компаниями, специализирующимися на средствах обработки XML документов. Первым главным разработчиком и инженером сопровождения был Давид Маггинсон.
Simple API for XML (SAX) не читает файлы - на вход создаваемому парсеру должна подаваться xml-строка. Нет проверки на валидность.
- Официальный сайт Simple API for XML;
- Описывает метод парсинга XML-документов для получения данных из них;
- Создавать и изменять XML-документы с помощью SAX невозможно;
- Основан на событиях;
- XML-парсеру предоставляется набор собственных функций для обработки различных типов XML-данных;
- Парсер автоматически вызывает эти функции в процессе последовательной обработки XML-документа.
Использование SAX:
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
// Создание парсера:
$sax = xml_parser_create("utf-8");
// Декларация функций обработки событий:
function onStart($parser,$tag,$attributes){}
function onEnd($parser, $tag){}
function onText($parser, $text){}
// Регистрация функций как обработчиков событий:
xml_set_element_handler($sax,"onStart","onEnd");
xml_set_character_data_handler($sax,"onText");
// Запуск парсера:
xml_parse($sax, "XML строка!");
// Обработка ошибок:
echo xml_error_string(xml_get_error_code($sax));
06Парсинг xml-документа с помощью SAX
Для примера выведем созданный выше файл '_php33/_classes/example_fb.xml' в виде таблицы с помощью SAX:
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
// // Создание парсера:
// $sax_parser=xml_parser_create("utf-8");
//
//
// // Функция обработчик открывающих тегов
// function opening_tag($parser,$tag,$attr){
// // по умолчанию $tag приводит имя элемента к верхнему регистру!!!
// if($tag=="CATALOG"){echo "<table class='blue_table'><caption>Каталог книг</caption><tr><th>№</th><th>автор</th><th>название</th><th>год</th><th>цена</th><th>валюта</th></tr>";
// }elseif($tag=="BOOK"){
// static $i_static=0;
// $i_static++;
// echo "<tr><td>$i_static</td>";
// }else{echo "<td>";}
// }
//
// // Функция обработчик закрывающих тегов
// function closing_tag($parser,$tag){
// if($tag=="CATALOG"){echo "</table>";
// }elseif($tag=="BOOK"){echo "</tr>";
// }else{echo "</td>";}
// }
//
// // Функция обработчик содержимого элементов
// function on_text($parser,$text){echo "$text";}
//
//
// // Привязка событий
// // Назначение обработчиков открывающих и закрывающих тегов
// xml_set_element_handler($sax_parser,"opening_tag","closing_tag");
//
// // Назначение обработчика содержимого элементов
// xml_set_character_data_handler($sax_parser,"on_text");
//
// // Запуск парсера
// xml_parse($sax_parser,file_get_contents("/full/path/to/_php33/_classes/example_fb.xml"));
//
// // Обработка ошибок
// // echo xml_error_string(xml_get_error_code($sax_parser));
// $err=xml_error_string(xml_get_error_code($sax_parser));
// if($err!="No error"){echo $err;}
// парсинг в переменную с помощью суперглобального массива $GLOBALS
$GLOBALS["res"]="";
// Создание парсера
$sax_parser=xml_parser_create("utf-8");
// Функция обработчик открывающих тегов
function opening_tag($parser,$tag,$attr){
// по умолчанию $tag приводит имя элемента к верхнему регистру!!!
if($tag=="CATALOG"){$GLOBALS["res"].="<table class='blue_table'><caption>Каталог книг</caption><tr><th>№</th><th>автор</th><th>название</th><th>год</th><th>цена</th><th>валюта</th></tr>";
}elseif($tag=="BOOK"){
static $i_static=0;
$i_static++;
$GLOBALS["res"].="<tr><td>$i_static</td>";
}else{$GLOBALS["res"].="<td>";}
}
// Функция обработчик закрывающих тегов
function closing_tag($parser,$tag){
if($tag=="CATALOG"){$GLOBALS["res"].="</table>";
}elseif($tag=="BOOK"){$GLOBALS["res"].="</tr>";
}else{$GLOBALS["res"].="</td>";}
}
// Функция обработчик содержимого элементов
function on_text($parser,$text){
$GLOBALS["res"].="$text";
}
// Привязка событий
// Назначение обработчиков открывающих и закрывающих тегов
xml_set_element_handler($sax_parser,"opening_tag","closing_tag");
// Назначение обработчика содержимого элементов
xml_set_character_data_handler($sax_parser,"on_text");
// Запуск парсера
xml_parse($sax_parser,file_get_contents("/full/path/to/_php33/_classes/example_fb.xml"));
// Обработка ошибок
// echo xml_error_string(xml_get_error_code($sax_parser));
$err=xml_error_string(xml_get_error_code($sax_parser));
if($err!="No error"){$GLOBALS["res"].= $err;}
// Вывод результата
echo $GLOBALS["res"];
№ | автор | название | год | цена | валюта |
---|---|---|---|---|---|
1 | Виктор Олегович Пелевин | Омон Ра | 1992 | 200 | RUB |
2 | Чарльз Майкл (Чак) Палагнюк | Бойцовский клуб | 1997 | 300 | RUB |
3 | Виктор Олегович Пелевин | Смотритель | 2015 | 300 | RUB |
4 | Клим Александрович Жуков | Почему не надо казнить чучело Солженицына | 2016 | 50 | RUB |
5 | Виктор Олегович Пелевин | Достающее звено. 2 тома | 2017 | 600 | RUB |
6 | Виктор Олегович Пелевин | Тайные виды на гору Фудзи | 2018 | 300 | RUB |
07DOM (Document Object Model)
(от англ. Document Object Model — «объектная модель документа») — это независящий от платформы и языка программный интерфейс, позволяющий программам и скриптам получить доступ к содержимому HTML-, XHTML- и XML-документов, а также изменять содержимое, структуру и оформление таких документов.
Модель DOM не накладывает ограничений на структуру документа. Любой документ известной структуры с помощью DOM может быть представлен в виде дерева узлов, каждый узел которого представляет собой элемент, атрибут, текстовый, графический или любой другой объект. Узлы связаны между собой отношениями «родительский-дочерний».
Изначально различные браузеры имели собственные модели документов (DOM), несовместимые с остальными. Для обеспечения взаимной и обратной совместимости специалисты международного консорциума W3C классифицировали эту модель по уровням, для каждого из которых была создана своя спецификация. Все эти спецификации объединены в общую группу, носящую название «W3C DOM».
История DOM переплетается с историей «браузерных войн» в конце 1990-х годов между Netscape Navigator и Microsoft Internet Explorer (и между первыми скриптовыми языками JavaScript и JScript) за то, чтобы широко использоваться в механизме вёрстки веб-страниц.
Язык программирования «JavaScript» был выпущен фирмой «Netscape Communications» в 1995 году в рамках веб-браузера «Netscape Navigator 2.0». Конкурент фирмы «Netscape» — фирма «Microsoft» выпустила позже в том же году веб-браузер «Internet Explorer 3.0» с портом JavaScript, названным «JScript». JavaScript и JScript позволяют разработчикам создавать веб-страницы, которые были бы интерактивны со стороны клиента. Ограниченные возможности обнаружения создаваемых пользователем событий и изменения документа HTML в первом поколении этих языков в итоге стали известны как «DOM уровня 0» или «традиционный DOM». Для DOM уровня 0 не было разработано никакого независимого стандарта, однако он был частично описан в спецификации HTML4.
Всё узел. Куда ни плюнь - везде узел. И сам документ тоже узел.
Название интерфейса | Код типа | Описание |
---|---|---|
ELEMENT | 1 | Узел элемента. Любой элемент на странице |
ATTR | 2 | Узел атрибута (возвращает набор атрибутов элемента XML- или HTML-документа) |
TEXT | 3 | Текстовый узел |
COMMENT | 8 | Узел комментария |
DOCUMENT | 9 | Объект Document. Узел документа (основа доступа к содержанию документа и создания его составляющих) |
DOCUMENTTYPE | 10 | Узел декларации типа документа (возвращает тип данного документа) |
DOCUMENTFRAGMENT | 11 | Несколько узлов, объединенных в один фрагмент (используется при создании и добавлении узлов в документ) |
Использование DOM:
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
// 1 Чтение XML-документа
// Создание объекта, экземпляра класса DomDocument
$dom = new DomDocument();
// Загрузка документа
$dom->load("catalog.xml");
// Получение коневого элемента
$root = $dom->documentElement;
// Получение типа узла
echo $root->nodeType; // 1
// Получение коллекции дочерних узлов (экземпляр класса DomNodeList)
$children = $root->childNodes;
// Получение текстового содержимого узла
$content = $root->textContent;
// Получение коллекции элементов с определённым именем
$books = $dom->getElementsByTagName("book");
// 2 Создание/изменение XML-документа
// Создание объекта, экземпляра класса DomDocument
$dom = new DomDocument("1.0", "utf-8");
// Получение коневого элемента
$root = $dom->documentElement;
// Создание новых элементов
$book = $dom->createElement("book");
$title = $dom->createElement("title");
// Создание текстового узла
$text = $dom->createTextNode("Название книги");
// Добавление узлов к узлам
$title->appendChild($text);
$book->appendChild($title);
$root->appendChild($book);
// Другой вариант создания нового элемента
$author = $dom->createElement("author", "Автор книги");
// Добавляем узел к узлу перед другим узлом ($author перед $title)
$book->insertBefore($author, $title);
// Создаём секцию CDATA
$description = $dom->createElement("description");
$cdata = $dom->createCDATASection("...описание книги...");
$description->appendChild($cdata);
$book->appendChild($description);
// Сохраняем документ
$dom->save("catalog.xml");
08Парсинг xml-документа с помощью DOM
Если при парсинге в SAX применялся процедурный стиль, то в DOM используется ООП.
Выведем файл '_php33/_classes/example_fb.xml' в виде таблицы с помощью DOM:
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// очистка содержимого переменной $res
$GLOBALS["res"]="";
// Создание объекта, экземпляра класса DomDocument
$dom = new DomDocument();
// Загрузка документа
$dom->load("/full/path/to/_php33/_classes/example_fb.xml");
// Получение коневого элемента
$root = $dom->documentElement;
// Получение коллекции дочерних узлов (экземпляр класса DomNodeList)
$books = $root->childNodes;
// var_dump($books); // object(DOMNodeList)#1 (1) { ["length"]=> int(14) }
// Получение доступа к вложенным в узел "BOOK" узлам через foreach()
$key=0;
foreach($books as $book){
// Проверка на тип полученного ребёнка. в $book будут также и текстовые узлы - переводы строк
// если дочерний узел является элементом (здесь - book)
if($book->nodeType==1){
$key++;
$GLOBALS["res"].="<tr><td>$key</td>";
// $GLOBALS["res"].=gettype($book); // object
foreach($book->childNodes as $childBook){
// если дочерний узел опять таки является элементом (здесь - author, title..)
if($childBook->nodeType==1){
$GLOBALS["res"].="<td>$childBook->textContent</td>";
}
}
$GLOBALS["res"].="</tr>";
}
}
$GLOBALS["res"]="<table class='blue_table'><caption>Каталог книг</caption><tr><th>№</th><th>автор</th><th>название</th><th>год</th><th>цена</th><th>валюта</th></tr>".$GLOBALS["res"]."</table>";
// Вывод результата
echo $GLOBALS["res"];
№ | автор | название | год | цена | валюта |
---|---|---|---|---|---|
1 | Виктор Олегович Пелевин | Омон Ра | 1992 | 200 | RUB |
2 | Чарльз Майкл (Чак) Палагнюк | Бойцовский клуб | 1997 | 300 | RUB |
3 | Виктор Олегович Пелевин | Смотритель | 2015 | 300 | RUB |
4 | Клим Александрович Жуков | Почему не надо казнить чучело Солженицына | 2016 | 50 | RUB |
5 | Виктор Олегович Пелевин | Достающее звено. 2 тома | 2017 | 600 | RUB |
6 | Виктор Олегович Пелевин | Тайные виды на гору Фудзи | 2018 | 300 | RUB |
09Парсер xml и html-сущности
Любой(?) парсер продолжает разбирать текстовый узел на предмет наличия в нём вложенных элементов (тип PCDATA - Parsed Character Data. Это не просто текст, а текст, который нужно распарсить) и неизбежно упадёт при встрече с html-сущностями. Парсер xml даже не подозревает о их существовании.
Для запрета дальнейшего парсинга и предотвращения падений парсера пользуют секцию CDATA - character data, символьные данные. CDATA показывает, что определённая часть документа представляет собой символьные данные, в отличие от несимвольных или символьных, но особым образом структурированных данных.
В XML документах фрагмент, помещённый внутрь CDATA, — это часть содержания элемента, которая помечена для парсера как содержащая только символьные данные, а не разметку. CDATA — это просто альтернативный синтаксис для отображения символьных данных, нет никакой смысловой разницы между символьными данными, которые объявлены как CDATA и символьными данными, которые объявлены в обычном синтаксисе и где '<' и '>' будут представлены как '<' и '>', соответственно.
Раздел CDATA начинается со следующей последовательности символов '<![CDATA[' и заканчивается с первым появлением последовательности ']]>'. Все символы, заключённые между этими двумя последовательностями, интерпретируются как символы, а не как разметка или ссылки на объект.
Новички в использовании XML часто неверно представляют назначение раздела CDATA, как «защиту» от обработки данных внутри этого раздела наряду с обычными символьными данными. Некоторые API для работы с XML документами предоставляют независимый доступ к разделу CDATA, но эти возможности существуют поверх и вне обычных требований к системе обработки XML и не меняют смысл этих данных. Символьные данные — это символьные данные, независимо от того, записаны они в раздел CDATA или же с помощью обычной разметки.
Раздел CDATA полезен, когда надо написать XML код как текстовые данные внутри XML документа. Например, если надо набрать книгу про XML на XSL с примерами использования XML приложений, то встречающиеся примеры будут заключены в CDATA. Однако, CDATA не может содержать строку ']]>' и, следовательно, невозможно создавать вложенные разделы CDATA. Если текст содержит ']]>', то можно его отобразить, используя, например, несколько разделов CDATA, оканчивая один из них перед символом '>': '<![CDATA[]]]]><![CDATA[>]]>'
10Лабораторная работа №3.1 Создание RSS с помощью DOM
Упражнение 1: Знакомство со структурой RSS-документа
- В текстовом редакторе откройте файл news/rss.txt и ознакомьтесь со структурой RSS-документа
- В текстовом редакторе откройте файл news/NewsDB.class.php
- Добавьте константу класса RSS_NAME для хранения имени RSS-файла, например, rss.xml
- Добавьте константу класса RSS_TITLE для хранения заголовка новостной ленты, например, Последние новости
- Добавьте константу класса RSS_LINK для хранения ссылки на саму новостную ленту - https://yakoffka.ru/news
- Создайте и опишите метод createRss(), который будет формировать RSS-документ
- Создайте объект $dom, экземпляр класса DOMDocument
- Напишите следующие строки для правильного форматирования документа:
'$dom->formatOutput = true;'
'$dom->preserveWhiteSpace = false;' - Создайте корневой элемент rss и привяжите его к объекту $dom
- Напишите следующие строки для создания атрибута version корневого элемента:
'$version = $dom->createAttribute('version');'
'$version->value = '2.0';'
'$rss->appendChild($version);' - Создайте элемент channel и привяжите его к корневому элементу
- Создайте элементы title и link, и привяжите их к элементу channel. Содержимое элементов находится в константах RSS_TITLE и RSS_LINK
- Получите данные в виде массива из базы данных и дальнейшие действия производите в цикле
- Cоздайте новый XML-элемент item для очередной новости
- Cоздайте XML-элементы для всех данных новостной ленты (вместе с текстовыми узлами): title, link, description, pubDate, category. Не забудьте обернуть текст для элемента description секцией CDATA
- Привяжите созданные XML-элементы с данными к XML-элементу item
- Привяжите XML-элемент item к элементу channel
- Сохраните файл. Имя файла - константа RSS_NAME
- Вызовите метод createRss() после добавления новости в методе saveNews()
- Сохраните файл news/NewsDB.class.php
- Запустите браузер
- Наберите в адресной строке браузера http://mysite.local/news/news.php
- Добавьте запись в новостную ленту
- Убедитесь, что в папке news появился файл rss.xml
- Откройте файл news/rss.xml и убедитесь, что данные записаны корректно
- Попробуйте добавить еще несколько записей в новостную ленту
Просмотрим содержимое файла news/rss.txt
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<rss version="2.0">
<channel>
<title>Заголовок новостной ленты</title>
<link>Ссылка на новостную ленту (абсолютный URL)</link>
<item>
<title>Заголовок новости</title>
<link>Ссылка на новость (абсолютный URL)</link>
<description>Содержание новости</description>
<pubDate>Дата публикации новости</pubDate>
<category>Категория новости</category>
</item>
</channel>
<channel>
<title>Заголовок новостной ленты</title>
<link>Ссылка на новостную ленту (абсолютный URL)</link>
<item>
<title>Заголовок новости</title>
<link>Ссылка на новость (абсолютный URL)</link>
<description>Содержание новости</description>
<pubDate>Дата публикации новости</pubDate>
<category>Категория новости</category>
</item>
</channel>
</rss>
Ввиду того, что необходимо интегрировать лабораторную работу в структуру сайта, мы пойдём немного другим путём, отличным от рекомендованного выше.
Для начала скопируем файлы новостной ленты, не требующие изменения (в итоге этими файлами оказались лишь файлы классов), из директории '_php32' в текущую директорию '_php33'.
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
$res="";
$arr_cf=array(
"/_classes/INewsDB.class.php",
"/_classes/NewsDB.class.php",
);
// проверка существования директории "/full/path/to/_php33_classes/"
$pd="/full/path/to/_php33/_classes";
if(!is_dir($pd)){if(!mkdir($pd,0777,true)){$res.="Не удалось создать директорию $pd.<br>\n";}}
foreach($arr_cf as $nf){
$source="/full/path/to/_php32$nf";
$dest="/full/path/to/_php33$nf";
if(is_file($source)){
if(copy($source,$dest)){
$res.="Файл $nf скопирован успешно.<br>\n";
}else{$res.="Не удалось скопировать файл $nf.<br>\n";}
}else{$res.="Файл $nf не найден.<br>\n";}
}
echo $res;
Файл /_classes/NewsDB.class.php скопирован успешно.
Теперь создадим класс NewsDB_RSS - наследник NewsDB, в котором опишем требуемые константы и метод createRss().
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
class NewsDB_RSS extends NewsDB{ // наследуем класс NewsDB
//--------------------------------------------------------------------------
// Добавление констант класса
//--------------------------------------------------------------------------
// Добавление константы класса RSS_NAME для хранения имени RSS-файла
const RSS_NAME = "/full/path/to/_php33/rss.xml";
// Добавление константы класса RSS_TITLE для хранения заголовка новостной ленты
const RSS_TITLE = "Последние новости";
// Добавление константы класса RSS_LINK для хранения ссылки на саму новостную ленту
const RSS_LINK = "https://yakoffka.ru/news";
//--------------------------------------------------------------------------
// описание метода createRss()
//--------------------------------------------------------------------------
private function createRss(){ // приватим метод по совету Игорь Олегыча
// Создание объекта $dom, экземпляра класса DOMDocument
$dom=new DOMDocument("1.0", "utf-8");
// правильное (читабельное) форматирование документа
// formatOutput Форматирует вывод, добавляя отступы и дополнительные пробелы
$dom->formatOutput = true;
// preserveWhiteSpace false - Указание убирать лишние пробелы и отступы. По умолчанию TRUE
$dom->preserveWhiteSpace = false;
// создание корневого элемента
$rss = $dom->createElement("rss");
// Добавление узла $rss к узлу $dom
$dom->appendChild($rss);
// создание атрибута version корневого элемента
$version = $dom->createAttribute("version");
$version->value = "2.0";
$rss->appendChild($version);
// создание элемента channel и привязка его к корневому элементу rss
$channel = $dom->createElement("channel");
// Добавление узла channel к узлу rss
$rss->appendChild($channel);
// создание элементов title и link и привязка их к элементу channel
$title = $dom->createElement("title", self::RSS_TITLE);
$link = $dom->createElement("link", self::RSS_LINK);
// Добавление узлов title и link к узлу channel
$channel->appendChild($title);
$channel->appendChild($link);
// получение данных в виде массива из db
// $arrNews=$this->getNews();
if(!$arrNews=$this->getNews()){return false;}
foreach($arrNews as $news){
// создание нового XML-элемента item для добавляемой новости
$item = $dom->createElement("item");
// создание XML-элементов для данных новостной ленты вместе с текстовыми узлами
$title = $dom->createElement("title");
$titleText = $dom->createTextNode($news["title"]);
$title->appendChild($titleText);
$category = $dom->createElement("category");
$categoryText = $dom->createTextNode($news["category"]);
$category->appendChild($categoryText);
$description = $dom->createElement("description");
$descriptionCDATASection = $dom->createCDATASection($news["description"]); // !!!
$description->appendChild($descriptionCDATASection);
$source = $dom->createElement("source");
$sourceText = $dom->createTextNode($news["source"]);
$source->appendChild($sourceText);
$pubDate = $dom->createElement("pubDate");
$pubDateText = $dom->createTextNode(date("Y.m.d",$news["datetime"]));
$pubDate->appendChild($pubDateText);
$link = $dom->createElement("link");
$linkText = $dom->createTextNode("https://yakoffka.ru/conspects/laboratory_3.3.1/".$news["id"]);
$link->appendChild($linkText);
// привязка XML-элементов к элементу item
$item->appendChild($title);
$item->appendChild($category);
$item->appendChild($description);
$item->appendChild($source);
$item->appendChild($pubDate);
$item->appendChild($link);
// привязка XML-элемента item к элементу channel !!!не описано!!!
$channel->appendChild($item);
}
// сохранение XML-документа
if($dom->save(self::RSS_NAME)){
return true;
}else{return false;}
}
//--------------------------------------------------------------------------
// добавление метода для выдачи определённой новости по её id
//--------------------------------------------------------------------------
// ПЕРЕПИСАТЬ!!!
function getIDNews($id){
$sql="SELECT msgs.id as id,
title,
category.name as category,
description,
source,
datetime
FROM msgs, category
WHERE category.id = msgs.category
ORDER BY msgs.id DESC";
$res = $this->_db->query($sql);
if($res){
return $this->db2arr($res);
}else{
return false;
}
}
//--------------------------------------------------------------------------
// переопределение методов класса для обновления ленты при добавлении либо удалении новости
//--------------------------------------------------------------------------
// переопределение метода saveNews()
function saveNews($title,$category,$description,$source){
if(parent::saveNews($title,$category,$description,$source)){ // выполнение перегруженного родительского метода
return $this->createRss();
}else{
return false;
}
}
// переопределение метода deleteNews()
function deleteNews($id){
if(parent::deleteNews($id)){ // выполнение перегруженного родительского метода
return $this->createRss();
}else{
return false;
}
}
}
Перепишем файл '_classes/save_rss_news.inc.php', изменив в нём Location.
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
$t=$news->clearStr($_POST["title"]);
$c=$news->clearStr($_POST["category"]);
$d=$news->clearStr($_POST["description"]);
$s=$news->clearStr($_POST["source"]);
if(empty($t) or empty($d)){
$errMsg.="Заполните все поля!<br>\n";
}else{
if(!$news->saveNews($t,$c,$d,$s)){
$errMsg.="Произошла ошибка при добавлении новости в db.<br>\n";
// вызов метода теперь производится в описании методов saveNews() и deleteNews()
// }elseif(!$news->createRss()){
// $errMsg.="Произошла ошибка при добавлении новости в rss.<br>\n";
}else{
header("Location: https://yakoffka.ru/conspects/laboratory_3.3.1");// избавляемся от $_POST
exit;
}
}
Перепишем файл '_php33/_classes/get_news.inc.php', в котором изменим form action и осуществим вывод краткого описания новости.
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
$result=$news->getNews();
if(!is_array($result)){
$errMsg.='Ошибка вывода ленты новостей.<br>\n';
$result->columnType(0);
$result->columnType(1);
$result->columnType(2);
$result->columnType(3);
}else{
echo '<p>новостей: '.count($result).'шт.</p>';
foreach($result as $key=>$arr_news){
// получение краткого описания новости
$short_description=substr($arr_news['description'],0,strrpos(substr($arr_news['description'],0,300)," "))." <a href='https://yakoffka.ru/conspects/laboratory_3.3.1/".$arr_news['id']."'>...читать</a>";
echo "
<div class='news'>
<h2>".$arr_news['title']."</h2>
<!--div class='id'>id=".$arr_news['id']."</div-->
<div class='datetime'>".date('Y.m.d',$arr_news['datetime'])." ".$arr_news['category']."</div>
<!--div class='description'>".$arr_news['description']."</div-->
<div class='short_description'>$short_description</div>
<div class='source'>источник: ".$arr_news['source']."</div>
<br>
<form action='https://yakoffka.ru/conspects/laboratory_3.3.1' method='post'>
<input type='hidden' name='del_news' value='".$arr_news['id']."'>
<input type='submit' value='удалить эту новость'></p>
</form>
</div>
";
}
}
Создадим файл '_php33/_classes/get_id_news.inc.php', отвечающий за выдачу определённой статьи.
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
$id=$_GET['get_id_news'];
$result=$news->getIDNews($id);
if(!is_array($result)){
$errMsg.='Ошибка вывода ленты новостей.<br>\n';
}else{
foreach($result as $key=>$arr_news){
if($arr_news['id']==$_GET['get_id_news']){
// разделение текста на абзацы по переносам строк
$description="<p>".preg_replace("~[\n]{1,}~u","</p><p>",$arr_news['description'])."</p>";
$from_inc.="
<div class='news'>
<h2>".$arr_news['title']."</h2>
<!--div class='id'>id=".$arr_news['id']."</div-->
<div class='datetime'>".date('Y.m.d',$arr_news['datetime'])." ".$arr_news['category']."</div>
<div class='description'>$description</div>
<div class='source'>источник: ".$arr_news['source']."</div>
</div>
<a href='https://yakoffka.ru/conspects/laboratory_3.3.1'>«к редактированию новостей»</a>
<a href='https://yakoffka.ru/conspects/laboratory_3.3.2'>«к ленте новостей»</a>
";
}
}
}
Изменим Location и в файле '_php33/_classes/delete_news.inc.php'.
02
03
04
05
06
07
08
09
10
11
$id=$news->clearInt($_POST['del_news']);
if($id>0){
if(!$news->deleteNews($id)){
$errMsg.="Ошибка удаления записи.<br>\n";
}else{
header("Location: https://yakoffka.ru/conspects/laboratory_3.3.1");// избавляемся от $_POST
exit;
}
}
Создаём основной файл новостной ленты с rss - видоизменённый файл новостной ленты. Также добавим правило RewriteRule в .htaccess для вновь создаваемого файла.
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
// реализуем метод автозагрузки
spl_autoload_register(function($class){
include(DIR_SRC_CONSP_PHP33."/_classes/$class.class.php");
});
$errMsg="";
$from_inc="";
$showAll=true;
$news=new NewsDB_RSS();
if($_SERVER['REQUEST_METHOD']=='POST'){
if($_POST['title']){
require DIR_SRC_CONSP_PHP33."/_classes/save_rss_news.inc.php";
}elseif($_POST['del_news']){
require DIR_SRC_CONSP_PHP33."/_classes/delete_news.inc.php";
}
}elseif($_SERVER['REQUEST_METHOD']=='GET'){
if($_GET['get_id_news']){
require DIR_SRC_CONSP_PHP33."/_classes/get_id_news.inc.php";
$showAll=false;
}
}
$mc.="
<p class='err_mess'><?php echo \$errMsg;?></p>
<?php
echo \$from_inc;
if(\$showAll){
echo \"
Примечание: По умолчанию выводится страница с формой добавления новостей и списком новостей, уже существующих в базе.
<h2>Добавление новости</h2>
<form action='https://yakoffka.ru/conspects/laboratory_3.3.1' method='post'>
<p>Заголовок новости: <input type='text' name='title' style='width:200px;'>
Выберите категорию:
<select name='category'>
<option value='1'>Наука</option>
<option value='2'>Образование</option>
<option value='3'>Культура</option>
</select>
Текст новости:</p>
<textarea name='description' cols='90' rows='10' style='width:100%'></textarea><br>
<p>Источник: <input type='text' name='source'> <input type='submit' value='Добавить'></p>
</form>
\";
require('/full/path/to/_php33/_classes/get_news.inc.php');
}
?>
";
Результат выполнения работы: laboratory_3.3.1.
11SimpleXML
Расширение SimpleXML предоставляет очень простой и легкий в использовании набор инструментов для преобразования XML в объект, с которым можно затем работать через его свойства и с помощью итераторов.
В PHP получить доступ к элементу в XML документе, содержащим в названии недопустимые символы (например, дефис), можно путем заключения данного имени элемента в фигурные скобки и апострофы.
Доступ к неуникальным элементам в SimpleXML. Если в одном родительском элементе существует несколько экземпляров дочерних элементов, то нужно применять стандартные методы итерации, например, foreach. Свойства не являются массивами. Это итерируемые объекты в виде массива.
Для сравнения элемента или атрибута со строкой или для передачи в функцию в качестве текста, необходимо привести его к строке, используя (string). В противном случае, PHP будет рассматривать элемент как объект.
Два элемента SimpleXMLElements считаются разными, даже если они указывают на один и тот же объект, начиная с PHP 5.2.0.
SimpleXML включает в себя встроенную поддержку XPath. Рекомендован к использованию.
Использование SimpleXML:
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// загрузка документа и преобразование его в объект
$sxml=simplexml_load_file("name_file.xml");
// Используйте функцию libxml_use_internal_errors() для того,
// чтобы подавить все ошибки XML,
// и функцию libxml_get_errors() для прохода по ним впоследствии.
// загрузка xml-строки и преобразование её в объект
$sxml=simplexml_load_string("string");
// получение текста нужного элемента
echo $sxml->book[1]->title;
// получение атрибута элемента
// Получить доступ к атрибуту элемента можно так же, как к элементам массива
echo $sxml->book[1]->title["lang"];
// добавление дочерних элементов
$character->addChild("name", "Mr. Parser");
// добавление атрибута элемента
$rating->addAttribute("type", "mpaa");
// изменение текста нужного элемента
$sxml->book[1]->title="новое название";
// преобразование объекта в строку
$str_xml=$sxml->asXML();
// запись строки в файл
file_put_contents("name_file.xml",$sxml);
PHP может преобразовывать XML-узлы из SimpleXML в формат DOM и наоборот. Этот пример показывает, как можно изменить DOM-элемент в SimpleXML.
02
03
04
05
06
07
08
09
10
11
12
$dom = new DOMDocument;
$dom->loadXML("<books><book><title>чепуха</title></book></books>");
if (!$dom) {
echo "Ошибка при разборе документа";
exit;
}
$books = simplexml_import_dom($dom);
echo $books->book[0]->title;
12Парсинг xml-документа с помощью SimpleXML
Выведем файл '_php33/_classes/example_fb.xml' в виде таблицы с помощью SimpleXML, предварительно добавив туда книгу и исправим вкравшуюся ошибку, изменив автора уже находящейся в нём книги:
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
$GLOBALS["res"]="";
// интерпретируем XML-файл в объект класса SimpleXMLElement со свойствами, содержащими данные,
// которые хранятся внутри (корневого документа?) XML-документа
// (см. http://php.net/manual/ru/function.simplexml-load-file.php)
$catalog=simplexml_load_file("/full/path/to/_php33/_classes/example_fb.xml");
// добавляем ещё одну книгу
$book=$catalog->addChild("book");
$book->addChild("author","Станислав Владимирович Дробышевский");
$book->addChild("title","Шанс для приматов");
$book->addChild("pubYear","2016");
$book->addChild("price","150");
$book->addChild("curr","RUB");
$GLOBALS["res"].="<table class='blue_table'><caption>Каталог книг</caption><tr><th>№</th><th>автор</th><th>название</th><th>год</th><th>цена</th><th>валюта</th></tr>";
$i=0;
foreach($catalog as $book){
//при нахождении книги с нужным названием меняем автора
if($book->title=="Достающее звено. 2 тома"){
$book->author="Станислав Владимирович Дробышевский";
}
$i++;
$GLOBALS["res"].=
"<tr><td>".$i.
"</td><td>".$book->author.
"</td><td>".$book->title.
"</td><td>".$book->pubYear.
"</td><td>".$book->price.
"</td><td>".$book->curr.
"</td></tr>";
}
$GLOBALS["res"].="</table>";
// Вывод результата
echo $GLOBALS["res"];
// Запись объекта в файл
file_put_contents("/full/path/to/_php33/_classes/example_fb_copy.xml",$catalog->asXML());
file_put_contents("/full/path/to/_php33/_classes/example_fb_copy.txt",$catalog->asXML());
№ | автор | название | год | цена | валюта |
---|---|---|---|---|---|
1 | Виктор Олегович Пелевин | Омон Ра | 1992 | 200 | RUB |
2 | Чарльз Майкл (Чак) Палагнюк | Бойцовский клуб | 1997 | 300 | RUB |
3 | Виктор Олегович Пелевин | Смотритель | 2015 | 300 | RUB |
4 | Клим Александрович Жуков | Почему не надо казнить чучело Солженицына | 2016 | 50 | RUB |
5 | Станислав Владимирович Дробышевский | Достающее звено. 2 тома | 2017 | 600 | RUB |
6 | Виктор Олегович Пелевин | Тайные виды на гору Фудзи | 2018 | 300 | RUB |
7 | Станислав Владимирович Дробышевский | Шанс для приматов | 2016 | 150 | RUB |
Выведем исправленный файл во фрейме для проверки:
2
3
4
echo '<iframe src="https://yakoffka.ru/src/conspects/_php33/_classes/example_fb_copy.xml" width="100%" height="70"></iframe>';
echo '<iframe src="https://yakoffka.ru/src/conspects/_php33/_classes/example_fb_copy.txt" width="100%" height="200"></iframe>';
13Лабораторная работа 3.3.2 Чтение RSS с помощью SimpleXML
- В текстовом редакторе откройте файл news/rss_reader.php
- Пересохраните этот файл как C:\Users\Public\OpenServer\domains\localhost ss_reader.php
- Создайте константу RSS_URL для хранения адреса RSS-потока со значением http://mysite.local/news/rss.xml
- Создайте константу FILE_NAME для хранения RSS-документа на локальном сервере со значением news.xml
- Обычно новости обновляются с какой-либо периодичностью. Поэтому нет необходимости каждую минуту (и даже секунду) дёргать файл с удалённого сервера. Лучше закешировать данные у себя на локальном сервере и обновлять их через определённый период. Для этого создайте и опишите кеширующую функцию download(), которая закачивает RSS-документ с адреса RSS_URL и сохраняет его на локальном сервере под именем FILE_NAME
- Проверьте, существует ли файл на на локальном сервере? Если НЕТ, то создайте его с помощью функции download()
- После заголовка первого уровня Последние новости зачитайте с помощью SimpleXML RSS-документ:
- Создайте объект - экземпляр класса SimpleXML и загрузите документ;
- В цикле выведите в произвольной форме новостную ленту.
- Осуществите проверку на необходимость загрузки свежего RSS-файла на локальный сервер с помощью функции download()
- Сохраните файл rss_reader.php
Выведем файл rss.txt' (копию rss.xml' для сохранения структуры) во фрейме для наглядности:
2
3
4
copy("/full/path/to/_php33/rss.xml","/full/path/to/_php33/rss.txt");
echo '<iframe src="https://yakoffka.ru/src/conspects/_php33/rss.txt" width="100%" height="200"></iframe>';
создание файла rss_reader.php:
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
// создание констант
const RSS_URL="https://yakoffka.ru/src/conspects/_php33/rss.xml"; // и так сойдёт
const FILE_NAME="/full/path/to/_php33/rss_local.xml"; // адрес RSS-документа на локальном сервере
const RSS_TTL=3600; // периодичность в секундах
$errMsg="";
function download($url,$fp){
$str=file_get_contents($url);
if($str){
if(file_put_contents($fp,$str)){$errMsg="лента новостей обновлена";
}else{$errMsg="не удалось создать файл 'FILE_NAME'.";}
}else{$errMsg="не удалось получить файл 'RSS_URL'.";}
return $errMsg;
}
if(!is_file(FILE_NAME) OR time()>filemtime(FILE_NAME)+RSS_TTL){
$errMsg.=download(RSS_URL,FILE_NAME);
}
// парсим в переменную с помощью суперглобального массива $GLOBALS
$GLOBALS["res"]="";
// Создание парсера:
$simplexml=simplexml_load_file(FILE_NAME);
$GLOBALS["res"].=$channel->title;
foreach($simplexml->channel->item as $item){
$GLOBALS["res"].="
<div class='lab_item'>
<h3 class='left'><span class='timestamp_writing'>{$item->pubDate} {$item->category}</span> <a class='no_decoration' href='{$item->link}'>«{$item->title}»</a></h3>
</div>
";
}
$mc.="
<p class='err_mess'><?php echo \$errMsg;?></p>
<h2>Просмотр ленты новостей</h2>
<?php
// Вывод результата
echo \$GLOBALS['res'];
?>
";
14Преобразование XML. XSL/T
XSL/T (от англ. Extensible Stylesheet Language /Transformations) - Стилевая технология, предназначенная для трансформации XML-документов в другие форматы. Ничего общего с CSS.
Таблицы стилей XSL создаются по правилам XML-документов. Таблицы стилей XSL состоят из набора шаблонов.
XLT-процессоры на основании xml-документа и шаблонов, в которых указаны правила преобразования узлов, формируют, например, html-документ.
Все современные браузеры имеют встроенные xsl-процессоры, то есть преобразование идёт на клиентской стороне. Но можно выполнить преобразование и на стороне сервера.
.
Использование xsl на стороне сервера:
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
// Загрузка исходного XML-документа
$xml = new DomDocument();
$xml->load ('catalog.xml');
// Загрузка таблицы стилей XSL
$xsl = new DomDocument();
$xsl -> load ('catalog.xsl');
// Создание XSLT-процессора и загрузка в него
таблицы стилей
$processor = new XSLTProcessor();
$processor -> importStylesheet($xsl);
// Выполнение преобразования
echo $processor -> transformToXML($xml);
Капустин Яков (2018.12.31 17:03)