PHP使用XMLReader搭配XMLWriter/SimpleXML为超大XML新增is_active标签问题
问题原因分析
XMLWriter方案问题
- 匹配到
<offer>节点时直接跳过了原<offer>节点的写入操作,直接创建<is_active>标签,导致原有<offer>结构丢失,子节点全部被包裹进错误的<is_active>节点内 - 插入位置逻辑错误,没有在
<offer>节点闭合前插入新增标签
SimpleXML方案问题
- 每次处理完单个
<offer>节点都调用asXML()直接覆盖输出文件,导致前面的节点被后续节点覆盖,最终仅保留最后一个节点 - 没有写入根节点
<offers>的结构,输出的XML不符合原有格式 - 未实现
<is_active>标签的CDATA格式要求
可行解决方案
推荐优先使用XMLWriter方案,全程流式读写内存占用稳定,适合GB级超大XML文件处理:
修正后XMLWriter代码
<?php require('xmlreader-iterators.php'); $xmlInputFile = 'feed_sample.xml'; $xmlOutputFile = 'output.xml'; $reader = new XMLReader(); $reader->open($xmlInputFile); $writer = new XMLWriter(); $writer->openUri($xmlOutputFile); $writer->startDocument('1.0', 'UTF-8'); $iterator = new XMLWritingIteration($writer, $reader); foreach ($iterator as $node) { // 匹配offer结束标签,先插入新增节点再写闭合标签 if ($node->nodeType === XMLReader::END_ELEMENT && $node->name === 'offer') { $writer->startElement('is_active'); $writer->writeCdata('true'); // 可根据业务逻辑修改为true/false $writer->endElement(); } // 写入当前节点原有内容 $iterator->write(); // 兼容处理自闭合offer节点的特殊情况 if ($node->nodeType === XMLReader::ELEMENT && $node->name === 'offer' && $node->isEmptyElement) { $writer->startElement('is_active'); $writer->writeCdata('true'); $writer->endElement(); } } $writer->endDocument(); $reader->close();
修正后SimpleXML代码
如果需要继续使用SimpleXML实现,可参考以下代码,同样为低内存流式处理:
<?php $fn = __DIR__ . '/feed_sample.xml'; $outputFn = __DIR__ . '/outputSimple.xml'; $tag = 'offer'; $reader = new \XMLReader(); if (!$reader->open($fn)) { throw new \RuntimeException("Could not open {$fn} with XMLReader"); } // 预先打开输出文件,写入XML头和根节点起始标签 $outputFp = fopen($outputFn, 'w'); fwrite($outputFp, '<?xml version="1.0" encoding="UTF-8"?>' . PHP_EOL . '<offers>' . PHP_EOL); while ($reader->read()) { if ($reader->name === $tag && $reader->nodeType === XMLReader::ELEMENT) { $elem = new \SimpleXMLElement($reader->readOuterXML()); // SimpleXML原生不支持直接添加CDATA节点,借助DOM实现 $dom = dom_import_simplexml($elem); $isActiveNode = $dom->ownerDocument->createElement('is_active'); $cdataContent = $dom->ownerDocument->createCDATASection('true'); $isActiveNode->appendChild($cdataContent); $dom->appendChild($isActiveNode); // 追加写入当前处理完成的offer节点,避免覆盖 fwrite($outputFp, preg_replace('/<\?xml.+\?>/', '', $elem->asXML()) . PHP_EOL); $reader->next($tag); } } // 写入根节点闭合标签,关闭资源 fwrite($outputFp, '</offers>'); fclose($outputFp); $reader->close();
内容的提问来源于stack exchange,提问作者Kacper Woźniak
相关产品推荐
相关产品推荐

