Эта тема на forum.dklab.ru


Антон Макаренко: Работа со сложным деревом XML
Допустим, существует документ XML, который необходимо преобразовать в массив-дерево в PHP. Структура документа не определена заранее — т.е. количество уровней вложенности неизвестно. Вопрос к участникам форума: есть ли у кого-нибудь готовые функции для парсинга документов XML с помощью DOM (+XPath)?

Вот пример исходного документа XML:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!DOCTYPE menu SYSTEM "test.dtd">
<menu>
<entry dir="about">
<caption lang="uk">Назва 1</caption>
<caption lang="en">Caption 1</caption>
<descr lang="uk">Опис 1</descr>
<entry dir="history">
<caption lang="uk">Назва 1.1</caption>
<descr lang="uk">Опис 1.1</descr>
</entry>
</entry>
<entry dir="structure">
<caption lang="uk">Назва 2</caption>
<descr lang="uk">Опис 2</descr>
</entry>
</menu>

Его DTD:
<?xml version="1.0" encoding="UTF-8"?>
<!-- elements -->
<!ELEMENT menu (entry)+>
<!ELEMENT entry ((caption)+, (descr)*, (entry)*)>
<!ELEMENT caption (#PCDATA)>
<!ELEMENT descr (#PCDATA)>
<!-- / elements -->
<!-- attlists -->
<!ATTLIST entry
dir CDATA #REQUIRED
>
<!ATTLIST caption
lang CDATA "uk"
>
<!ATTLIST descr
lang CDATA "uk"
>
<!-- / attlists -->

А ниже — тупой перебор, ограниченный двумя уровнями — то, что мне удалось придумать, почитав документацию и пару статей об DOM XML и XPath:
<?php
echo '<pre>';
$lang='uk';
// в данном случае все файлы лежат в htdocs сервера
$source=domxml_open_file($_SERVER['DOCUMENT_ROOT'].'/test.xml');
$xps=xpath_new_context($source);
$root=$source->document_element();
//
$dump=array();
// lvl1
$expr='/'.$root->tagname.'/entry';
$res=xpath_eval($xps,$expr);
foreach ($res->nodeset as $node)
$dump[]=$node;
// lvl2
foreach ($dump as $key=>$node)
{
$expr='/'.$root->tagname.'/entry[@dir="'.$node->get_attribute('dir').'"]/entry';
if ($res=xpath_eval($xps,$expr))
$dump[$key]->children=$res->nodeset;
}
echo('<hr />');
print_r($dump);
echo '</pre>';
?>

Если кому интересно знать, зачем вся эта канитель с XML, отвечу: я пытаюсь придумать структуру сайта, основанную на XML (что-то типа http://seat-club.kiev.ua/sitemap/ — там сейчас структура хранится в БД). Почему XML? Потому что это идеальный вариант для представления древовидной структуры данных (да что вам объяснять, вы и сами всё знаете (-: ).

P.S. Предистория: http://forum.dklab.ru/php/heap/LevelsOfHierarchicalStructure.html
Там описана работа с деревом, представленным в виде одной таблицы БД. Если нужно, могу привести работающий код, который вытягивает данные из БД и генерит дерево с любым количеством уровней вложенности с помощью рекурсивных функций.
P.P.S. И ещё одна причина попытки перейти на XML: возможность грамотной интернационализации структуры данных.

В Опере на работает приаттачивание файлов :-(

Алексей Пешков: Re: Работа со сложным деревом XML
Допустим, существует документ XML, который необходимо преобразовать в массив-дерево в PHP. Почему XML? Потому что это идеальный вариант для представления древовидной структуры данных
А не лучше ли хранить дерево в виде сериализованного массива PHP?
Или, наоборот, использовать DOM (SAX, XSL, SimpleXML и т. д.) непосредственно в нужном месте, без предварительных преобразований "туды-сюды"?
Антон Макаренко:
Давайте не будем трогать сериализованные массивы, ок? Они не имеют отношения к XML.
Что значит "использовать DOM (SAX, XSL, SimpleXML и т. д.) непосредственно в нужном месте"? Обьясните, как использовать его в "нужном месте" для неограниченной вложенности дерева XML?
Антон Макаренко:
Добрый вечер.
Попробовал я написать класс для работы с XML-файлами. Выкладываю результат двухдневной работы (см. архивом, поскольку код громоздкий для выкладывания на форум).
Идея состоит в использовании "массивов-шаблонов" для анализа и модификации документа.
Приведу пример использования. Если кого-нибудь заинтересует, завтра расскажу подробнее (сейчас времени нет (-: ).
Итак, представим себе некоторую "новостную ленту" в виде XML-файла:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE news [
<!ELEMENT news (item)+>
<!ELEMENT item (headline , text , author , image*)>
<!ELEMENT headline (#PCDATA)>
<!ELEMENT text (#PCDATA)>
<!ELEMENT author (#PCDATA)>
<!ELEMENT image EMPTY>
<!ATTLIST item lang (uk | en | ru) #REQUIRED>
<!ATTLIST item timestamp CDATA #REQUIRED>
<!ATTLIST item type CDATA #REQUIRED>
<!ATTLIST image filename CDATA #REQUIRED>
<!ATTLIST image alt CDATA #REQUIRED>
]>
<news>
<item lang="ru" timestamp="1126707522" type="common">
<headline>Заголовок новости</headline>
<text>Текстовое содеÑ?жание новости</text>
<author>1</author>
<image filename="img1.gif" alt="КаÑ?тинка 1"/>
<image filename="img2.jpg" alt="КаÑ?тинка 2"/>
</item>
</news>
(Содержимое файла в UTF-8, поэтому так коряво вставился. В архиве всё ок)

Попробуем поиграться с ним с помощью класса:
<?php
// example.php
require($_SERVER['DOCUMENT_ROOT'].'/domxmlhandler.class.php');
$dxp=new DOMXMLHandler($_SERVER['DOCUMENT_ROOT'].'/news.xml','windows-1251');

/*
Åñëè êòî íå äîãàäàëñÿ...
×òîá çàêîììåíòèðîàâòü áëîê, äîñòàòî÷íî â íà÷àëå óáðàòü îäèí "/"
×òîá ðàñêîììåíòàðîâàòü — ñîîòâåòñòâåííî, äîáàâèòü
*/

//* append
$appTemplate=array(
'lang'=>'ru',
'timestamp'=>1126707500,
'type'=>'common',
'headline'=>array(XML_TEXT_NODE=>'Çàãîëîâîê 2'),
'text'=>array(XML_TEXT_NODE=>'Òåêñòîâîå ñîäåðæàíèå'),
'author'=>array(XML_TEXT_NODE=>'Èìÿ àâòîðà'),
'image'=>array(
array('filename'=>'img3.jpg','alt'=>'Êàðòèíêà 3'),
array('filename'=>'img4.jpg','alt'=>'Êàðòèíêà 4')
)
);
$dxp->appendByTemplateR($dxp->root,$appTemplate,'item');
//*/

//* unlink
$dxp->unlinkByExpr('/'.$dxp->root->tagname.'/item[@timestamp="1126707500"]');
//*/

//* extract
$extrTemplate=array(
'lang'=>XML_ATTRIBUTE_NODE,
'timestamp'=>XML_ATTRIBUTE_NODE,
'type'=>XML_ATTRIBUTE_NODE,
'headline'=>array('contents'=>true),
'text'=>array('contents'=>true),
'author'=>array('contents'=>true),
'image'=>array(array('filename'=>XML_ATTRIBUTE_NODE,'alt'=>XML_ATTRIBUTE_NODE))
);
$exprs='/'.$dxp->root->tagname.'/item[@lang="ru"]';
print_r($dxp->extractByTemplate($exprs,$extrTemplate));
//*/

//* dump file
$dxp->domsrc->dump_file($_SERVER['DOCUMENT_ROOT'].'/news.xml',false,true);
//*/
?>

P.S. напоследок вопрос к опытным программистам: как вы считаете, стОит ли внутри отделить извлечение данных из объектов от парсинга документа? (см. метод extractByTemplate) т.е. может ли пригодиться отдельно массив обьектов или нет?
Антон Макаренко:
Вниманию желающих попользоваться кодом, который выложен выше
Пока не пользуйтесь :-)
Как оказалось, он ещё настолько сырой, что пригоден для использования только в целях ознакомления.
Например, я определил баг, из-за которого программа безбожно "валится". Он заключается в использовании подстановочных ссылок внутри содержимого элементов.
В частности, парсер валится на следующем моменте:
<text>а&nbsp;также о&nbsp;средствах связи</text>
Если же поменять &nbsp; на &#160;, то всё работает нормально.
T.е. парсер расценивает подстановочные ссылки, как элементы DOM и при этом теряет соответствие шаблону.

Вижу два варианта решения проблемы:
1) принудительно добавлять к содержимому куски XML: '<![CDATA['.$contents.']]>'
2) добавить блок анализа содержимого и заменять подстановочные ссылки на соответствующие символы (!?)

Даже не знаю, как быть...

Подскажите плз. ещё варианты, если есть идеи. (эти два — не очень-то хороший выход из ситуации).
Антон Макаренко:
С содержимым элементов разобрался. Решил воспользоваться-таки CDATA.
Только простое добавление строки '<![CDATA['...']]>' к содержимому не помогает.

Итак, как было:
$new->set_content($this->_encode($value));
Стало так:
$new->append_child($this->domsrc->create_cdata_section($this->_encode($value)));

Таким образом в содержимом каждого элемента, который определён как XML_TEXT_NODE, теперь может содержаться всё что угодно и парситься оно будет как CDATA

Эта тема на forum.dklab.ru