PHP XML Expat解析器:如何仅读取XML文档的一部分?

4
我有一个具有以下结构的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>";
}

дҪҝз”ЁXMLReaderд»Јжӣҝexpatи§ЈжһҗеҷЁпјҢиҝҷж ·еҸҜиЎҢеҗ—пјҹ - hakre
我更喜欢使用Expat解析器,它是PHP本地的,可以处理大型XML文件,而且是基于事件的解析器,而不是DOM。我发现它快速强大,特别是我喜欢xml_set_element_handler函数,它可以帮助轻松定义开始和结束标签。我相信一定有读取文档部分的选项! - razz
XMLReader 是 PHP 原生支持的,可以处理大型 XML 文件,它是一种 XML Pull 解析器。该阅读器充当游标,在文档流上向前移动并在途中停留在每个节点上。至于 Expat:没有这样的选项,但对于 XMLReader 是有的;这就是我为什么在问的原因。 - hakre
如果它不是DOM解析器,不会使用大量内存,不需要安装,速度快,并且没有其他方式可以为我完成工作...那么XMLReader将非常好,如果您能向我展示如何使用它来解决我的问题,我将不胜感激 :) - razz
这个答案展示了如何将特定元素转换为它自己的XML块(在这里仅作为SimpleXMLElement 一些元素):https://dev59.com/vG_Xa4cB1Zd3GeqP14E9#15351723 - hakre
2个回答

8
正如先前评论中所建议的那样,您也可以选择使用XMLReaderDocs代替SAX解析器。

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属性中按顺序编号,从1开始:

$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";
}

以下代码将返回ID为3和8的用户的XML:

---------------
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>
XMLReaderNodeXMLReader迭代器的一部分,同时也提供了一个SimpleXMLElement文档,以便您轻松读取<user>元素内的值。以下示例显示如何获取<user>元素内<post>元素的计数:
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";
}

对于用户ID 3,这将显示职位数量:1,对于用户ID 8,这将显示职位数量:3

然而,如果外部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 的子集上使用通用的 XML 解析器,如 SimpleXML 或者 DOMDocument


它可以运行,但是它把结果都打印在一行中:client1 - message1 - time1 - client2 - message2 - time2.... 是否有办法可以自定义输出,例如 如果($client = "operater"){echo <td class='class1'>message</td><td class='class2'>time</td>} else {....} - razz
当然,输出并不受限制。我在示例中只使用纯文本以保持简洁,但如果您喜欢,也可以使用HTML。 - hakre
我更新了XML文件并添加了一个foreach循环,想要使用它。经过一些测试,我发现if ($post->getChildElements('client') == "operater")是有效的,但它没有打印出消息或时间! - razz
1
好的,我现在可以看到了,这就是我之前说过的。当你使用 $post->getChildElements('client') 迭代子元素时,读取器已经到达了 <client> 元素。这意味着你无法再访问 <message>,因为它在 <client> 之前。相反,使用 asSimpleXML()toArray() 功能。它会存储所有这些值,你可以轻松地处理它们。 - hakre
让我们在聊天中继续这个讨论:http://chat.stackoverflow.com/rooms/26292/discussion-between-hakre-and-razzak - hakre
显示剩余2条评论

0

你可以使用PHP SimpleDomHTML(一个用PHP5+编写的HTML DOM解析器,让您以非常简单的方式操纵HTML!)您可以像使用jQuery一样查询数据。它支持HTML,所以肯定也支持XML文档。

您可以在此下载和查看文档:http://simplehtmldom.sourceforge.net/


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接