PHP XML Expat parser: как читать только часть документа XML?

У меня есть XML-документ со следующей структурой:

<posts> <user id="1222334"> <post> <message>hello</message> <client>client</client> <time>time</time> </post> <post> <message>hello client how can I help?</message> <client>operator</client> <time>time</time> </post> </user> <user id="2333343"> <post> <message>good morning</message> <client>client</client> <time>time</time> </post> <post> <message>good morning how can I help?</message> <client>operator</client> <time>time</time> </post> </user> </posts> 

Я могу создать парсер и распечатать весь документ, однако проблема заключается в том, что я хочу напечатать только (пользовательский) узел и дочерние элементы с определенным атрибутом (id).

мой PHP-код:

 if( !empty($_GET['id']) ){ $id = $_GET['id']; $parser=xml_parser_create(); function start($parser,$element_name,$element_attrs) { switch($element_name) { case "USER": echo "-- User --<br>"; break; case "CLIENT": echo "Name: "; break; case "MESSAGE": echo "Message: "; break; case "TIME": echo "Time: "; break; case "POST": echo "--Post<br> "; } } function stop($parser,$element_name){ echo "<br>"; } function char($parser,$data){ echo $data; } xml_set_element_handler($parser,"start","stop"); xml_set_character_data_handler($parser,"char"); $file = "test.xml"; $fp = fopen($file, "r"); while ($data=fread($fp, filesize($file))) { xml_parse($parser,$data,feof($fp)) or die (sprintf("XML Error: %s at line %d", xml_error_string(xml_get_error_code($parser)), xml_get_current_line_number($parser))); } xml_parser_free($parser); } 

используя это в функции start() можно выбрать правильный узел, но это не влияет на процесс чтения:

  if(($element_name == "USER") && $element_attrs["ID"] && ($element_attrs["ID"] == "$id")) 

любая помощь будет оценена

UPDATE: XMLReader работает, но при использовании оператора if он перестает работать:

 foreach ($filteredUsers as $user) { echo "<table border='1'>"; foreach ($user->getChildElements('post') as $index => $post) { if( $post->getChildElements('client') == "operator" ){ printf("<tr><td class='blue'>%s</td><td class='grey'>%s</td></tr>", $post->getChildElements('message'), $post->getChildElements('time')); }else{ printf("<tr><td class='green'>%s</td><td class='grey'>%s</td></tr>", $post->getChildElements('message'), $post->getChildElements('time')); } } echo "</table>"; } 

Как было предложено в комментарии ранее, вы также можете использовать XMLReader Docs .

Расширение XMLReader является парсером XML Pull. Читатель действует как курсор, идущий вперед по потоку документа и останавливаясь на каждом узле по пути.

Это класс (с тем же именем: XMLReader ), который может открыть файл. По умолчанию вы используете next() для перехода к следующему узлу. Затем вы должны проверить, находится ли текущая позиция в элементе, а затем, если элемент имеет имя, которое вы ищете, а затем вы можете его обработать, например, прочитав внешний XML элемента XMLReader::readOuterXml() Docs .

По сравнению с обратными вызовами в синтаксическом анализаторе Expat это немного обременительно. Чтобы получить большую гибкость с XMLReader я обычно создаю себе итераторы, которые могут работать с объектом XMLReader и предоставлять необходимые мне шаги .

Они позволяют перебирать бетонные элементы непосредственно с помощью foreach . Вот такой пример:

 require('xmlreader-iterators.php'); // https://gist.github.com/hakre/5147685 $xmlFile = '../data/posts.xml'; $ids = array(3, 8); $reader = new XMLReader(); $reader->open($xmlFile); /* @var $users XMLReaderNode[] - iterate over all <user> elements */ $users = new XMLElementIterator($reader, 'user'); /* @var $filteredUsers XMLReaderNode[] - iterate over elements with id="3" or id="8" */ $filteredUsers = new XMLAttributeFilter($users, 'id', $ids); foreach ($filteredUsers as $user) { printf("---------------\nUser with ID %d:\n", $user->getAttribute('id')); echo $user->readOuterXml(), "\n"; } 

Я создал XML-файл, содержащий несколько сообщений, например, в вашем вопросе, пронумерованных в атрибуте id от одного и выше:

 $xmlFile = '../data/posts.xml'; 

Затем я создал массив с двумя значениями ID интересующего пользователя:

 $ids = array(3, 8); 

Он будет использоваться в условиях фильтра позже. Затем XMLReader и открывается XML-файл:

 $reader = new XMLReader(); $reader->open($xmlFile); 

Следующий шаг создает итератор по всем элементам <user> этого читателя:

 $users = new XMLElementIterator($reader, 'user'); 

Затем они фильтруются для значений атрибутов id хранящихся ранее в массиве:

 $filteredUsers = new XMLAttributeFilter($users, 'id', $ids); 

Остальное теперь повторяется с foreach когда формулируются все условия:

 foreach ($filteredUsers as $user) { printf("---------------\nUser with ID %d:\n", $user->getAttribute('id')); echo $user->readOuterXml(), "\n"; } 

который вернет XML пользователей с идентификаторами 3 и 8:

 --------------- User with ID 3: <user id="3"> <post> <message>message</message> <client>client</client> <time>time</time> </post> </user> --------------- User with ID 8: <user id="8"> <post> <message>message 8.1</message> <client>client</client> <time>time</time> </post> <post> <message>message 8.2</message> <client>client</client> <time>time</time> </post> <post> <message>message 8.3</message> <client>client</client> <time>time</time> </post> </user> 

XMLReaderNode который является частью итераторов XMLReader , также предоставляет документы SimpleXMLElement если вы хотите легко прочитать значения внутри элемента <user> .

В следующем примере показано, как получить количество элементов <post> внутри элемента <user> :

 foreach ($filteredUsers as $user) { printf("---------------\nUser with ID %d:\n", $user->getAttribute('id')); echo $user->readOuterXml(), "\n"; echo "Number of posts: ", $user->asSimpleXML()->post->count(), "\n"; } 

Затем отобразится Number of posts: 1 для ID пользователя 3 и Number of posts: 3 для идентификатора пользователя 8.

Однако, если внешний XML большой, вы не хотите этого делать, и вы хотите продолжить итерацию внутри этого элемента:

 // rewind $reader->open($xmlFile); foreach ($filteredUsers as $user) { printf("---------------\nUser with ID %d:\n", $user->getAttribute('id')); foreach ($user->getChildElements('post') as $index => $post) { printf(" * #%d: %s\n", ++$index, $post->getChildElements('message')); } echo "Number of posts: ", $index, "\n"; } 

Который производит следующий вывод:

 --------------- User with ID 3: * #1: message 3 Number of posts: 1 --------------- User with ID 8: * #1: message 8.1 * #2: message 8.2 * #3: message 8.3 Number of posts: 3 

В этом примере показано: в зависимости от того, насколько велики вложенные дети, вы можете пройти дальше с помощью итераторов, доступных через getChildElements() или вы можете использовать также общий синтаксический анализатор XML, такой как SimpleXML или даже DOMDocument для подмножества XML.

Вы можете использовать PHP SimpleDomHTML (парсер HTML DOM, написанный на PHP5 +, позволяет вам управлять HTML очень простым способом!). Вы можете запросить свои данные так же, как вы работаете с jQuery. Он поддерживает HTML, поэтому он точно поддерживает XML-документ.

Вы можете скачать и просмотреть документ здесь: http://simplehtmldom.sourceforge.net/