2013-03-15 3 views
4

У меня есть документ XML со следующей структурой:PHP XML Expat parser: как читать только часть документа 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")) 

любая помощь оцениваться

ОБНОВЛЕНИЕ: 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>"; 
} 
+0

Можно ли использовать ['XMLReader'] (http://php.net/book.xmlreader) вместо парсера expat? – hakre

+0

Я предпочитаю использовать парсер Expat, он является родным для PHP и может обрабатывать большие XML-файлы, это также основанный на события парсер, а не DOM. Я нахожу его очень мощным, и мне особенно нравится функция 'xml_set_element_handler', которая помогает легко определять начальный и конечный теги. я уверен, что должна быть возможность прочитать часть документа! – razzak

+0

'XMLReader' является родным для PHP и может обрабатывать большие XML-файлы, это парсер XML Pull. Читатель действует как курсор, идущий вперед по потоку документа и останавливаясь на каждом узле по пути. И для Expat: Нет, такого варианта нет, но для XMLReader есть;) Вот почему я спрашиваю. – hakre

ответ

8

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

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

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

По сравнению с обратными вызовами в синтаксическом анализаторе Expat это немного обременительно. Чтобы получить большую гибкость с XMLReader Я обычно создаю себя iterators that are able to work on the XMLReader object and provide the steps I need.

Они позволяют перебирать бетонные элементы непосредственно с помощью 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'; 

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

$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, которая является частью the XMLReader iterators делает также обеспечить SimpleXMLElementDocs в случае вы хотите легко прочитать значения внутри элемента <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 для идентификатора пользователя 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.

+0

работает, но печатает результаты в одной строке: 'client1 - message1 - time1 - client2 - message2 - time2 ....'есть способ, которым я могу настроить вывод как' if ($ client = "operater") {echo message time)} else {... .} '? – razzak

+0

уверен, что вы не ограничены выходом. Я просто использую простой текст в этом примере, чтобы сохранить его небольшим, но вы можете использовать HTML вместо этого, если хотите. – hakre

+0

Я попытался использовать этот 'if ($ post-> getChildElements ('client') ==" operater ") {...} else {...}' и этот 'if ($ post-> getChildElements ('client') -> item (0) == "operater") {...} else {...} 'в' foreach ($ filtersUsers as $ user) ', но он не работает! – razzak

0

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

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

Смежные вопросы