如何根据 XML 节点中的记录编辑 PHP 中的大型 XML 文件
How to edit large XML files in PHP based on a record in the XML Node
我正在尝试通过 PHP 修改 130mb+ XML 文件,因此它只显示子节点为特定值的结果。由于我们用于将 XML 导入我们网站的软件的限制,我正在尝试过滤它。
示例:(模型数据)
<Items>
<Item>
<Barcode>...</Barcode>
<BrandCode>...</BrandCode>
<Title>...</Title>
<Content>...</Content>
<ShowOnWebsite>false</BrandDescr>
</Item>
<Item>
<Barcode>...</Barcode>
<BrandCode>...</BrandCode>
<Title>...</Title>
<Content>...</Content>
<ShowOnWebsite>true</BrandDescr>
</Item>
<Item>
<Barcode>...</Barcode>
<BrandCode>...</BrandCode>
<Title>...</Title>
<Content>...</Content>
<ShowOnWebsite>false</BrandDescr>
</Item>
</Items>
想要的结果:
我想创建一个新的 XML 文件,其中仅包含子项“ShowOnWebsite”为真的记录。
我运行遇到的问题
因为 XML 太大,简单的解决方案(例如使用 SimpleXML 或将 XML 加载到正文中并编辑其中的节点是行不通的。因为他们都是把整个文件读入内存,速度太慢,经常失败。
我还查看了 prewk/xml-string-streamer (https://github.com/prewk/xml-string-streamer),它非常适合流式传输大型 XML 文件,因为它不会将它们放在内存中,尽管我不能通过该解决方案找到任何修改 XML 的方法。 (其他在线帖子说您需要将节点保存在内存中才能对其进行编辑)。
有人知道如何解决这个问题吗?
目标
Desired result: I want to create a new XML file with only the records where the child "ShowOnWebsite" is true.
给出
test.xml
<Items>
<Item>
<Barcode>...</Barcode>
<BrandCode>...</BrandCode>
<Title>...</Title>
<Content>...</Content>
<ShowOnWebsite>false</ShowOnWebsite>
</Item>
<Item>
<Barcode>...</Barcode>
<BrandCode>...</BrandCode>
<Title>...</Title>
<Content>...</Content>
<ShowOnWebsite>true</ShowOnWebsite>
</Item>
<Item>
<Barcode>...</Barcode>
<BrandCode>...</BrandCode>
<Title>...</Title>
<Content>...</Content>
<ShowOnWebsite>false</ShowOnWebsite>
</Item>
</Items>
代码
这是我写的实现。 getItems
产生子节点而不立即将 xml 加载到内存中。
function getItems($fileName) {
if ($file = fopen($fileName, "r")) {
$buffer = "";
$active = false;
while(!feof($file)) {
$line = fgets($file);
$line = trim(str_replace(["\r", "\n"], "", $line));
if($line == "<Item>") {
$buffer .= $line;
$active = true;
} elseif($line == "</Item>") {
$buffer .= $line;
$active = false;
yield new SimpleXMLElement($buffer);
$buffer = "";
} elseif($active == true) {
$buffer .= $line;
}
}
fclose($file);
}
}
$output = new SimpleXMLElement('<?xml version="1.0" encoding="utf-8"?><Items></Items>');
foreach(getItems("test.xml") as $element)
{
if($element->ShowOnWebsite == "true") {
$item = $output->addChild('Item');
$item->addChild('Barcode', (string) $element->Barcode);
$item->addChild('BrandCode', (string) $element->BrandCode);
$item->addChild('Title', (string) $element->Title);
$item->addChild('Content', (string) $element->Content);
$item->addChild('ShowOnWebsite', $element->ShowOnWebsite);
}
}
$fileName = __DIR__ . "/test_" . rand(100, 999999) . ".xml";
$output->asXML($fileName);
输出
<?xml version="1.0" encoding="utf-8"?>
<Items><Item><Barcode>...</Barcode><BrandCode>...</BrandCode><Title>...</Title><Content>...</Content><ShowOnWebsite>true</ShowOnWebsite></Item></Items>
XMLReader
有一个 expand()
方法,但是 XMLWriter
缺少对应的方法。所以我在FluentDOM.
中添加了一个XMLWriter::collapse()
方法
这允许使用 XMLReader 读取 XML,将其扩展为 DOM,使用 DOM 方法将其 filter/manipulate 并写入返回 XML作者:
require __DIR__.'/../../vendor/autoload.php';
// Create the target writer and add the root element
$writer = new \FluentDOM\XMLWriter();
$writer->openUri('php://stdout');
$writer->setIndent(2);
$writer->startDocument();
$writer->startElement('Items');
// load the source into a reader
$reader = new \FluentDOM\XMLReader();
$reader->open(getXMLAsURI());
// iterate the Item elements - the iterator expands them into a DOM node
foreach (new FluentDOM\XMLReader\SiblingIterator($reader, 'Item') as $item) {
/** @var \FluentDOM\DOM\Element $item */
// only "ShowOnWebsite = true"
if ($item('ShowOnWebsite = "true"')) {
// write expanded node to the output
$writer->collapse($item);
}
}
$writer->endElement();
$writer->endDocument();
function getXMLAsURI() {
$xml = <<<'XML'
<Items>
<Item>
<Barcode>...</Barcode>
<BrandCode>...</BrandCode>
<Title>...</Title>
<Content>...</Content>
<ShowOnWebsite>false</ShowOnWebsite>
</Item>
<Item>
<Barcode>...</Barcode>
<BrandCode>...</BrandCode>
<Title>...</Title>
<Content>...</Content>
<ShowOnWebsite>true</ShowOnWebsite>
</Item>
<Item>
<Barcode>...</Barcode>
<BrandCode>...</BrandCode>
<Title>...</Title>
<Content>...</Content>
<ShowOnWebsite>false</ShowOnWebsite>
</Item>
</Items>
XML;
return 'data://text/plain;base64,'.base64_encode($xml);
}
我正在尝试通过 PHP 修改 130mb+ XML 文件,因此它只显示子节点为特定值的结果。由于我们用于将 XML 导入我们网站的软件的限制,我正在尝试过滤它。
示例:(模型数据)
<Items>
<Item>
<Barcode>...</Barcode>
<BrandCode>...</BrandCode>
<Title>...</Title>
<Content>...</Content>
<ShowOnWebsite>false</BrandDescr>
</Item>
<Item>
<Barcode>...</Barcode>
<BrandCode>...</BrandCode>
<Title>...</Title>
<Content>...</Content>
<ShowOnWebsite>true</BrandDescr>
</Item>
<Item>
<Barcode>...</Barcode>
<BrandCode>...</BrandCode>
<Title>...</Title>
<Content>...</Content>
<ShowOnWebsite>false</BrandDescr>
</Item>
</Items>
想要的结果: 我想创建一个新的 XML 文件,其中仅包含子项“ShowOnWebsite”为真的记录。
我运行遇到的问题 因为 XML 太大,简单的解决方案(例如使用 SimpleXML 或将 XML 加载到正文中并编辑其中的节点是行不通的。因为他们都是把整个文件读入内存,速度太慢,经常失败。
我还查看了 prewk/xml-string-streamer (https://github.com/prewk/xml-string-streamer),它非常适合流式传输大型 XML 文件,因为它不会将它们放在内存中,尽管我不能通过该解决方案找到任何修改 XML 的方法。 (其他在线帖子说您需要将节点保存在内存中才能对其进行编辑)。
有人知道如何解决这个问题吗?
目标
Desired result: I want to create a new XML file with only the records where the child "ShowOnWebsite" is true.
给出
test.xml
<Items>
<Item>
<Barcode>...</Barcode>
<BrandCode>...</BrandCode>
<Title>...</Title>
<Content>...</Content>
<ShowOnWebsite>false</ShowOnWebsite>
</Item>
<Item>
<Barcode>...</Barcode>
<BrandCode>...</BrandCode>
<Title>...</Title>
<Content>...</Content>
<ShowOnWebsite>true</ShowOnWebsite>
</Item>
<Item>
<Barcode>...</Barcode>
<BrandCode>...</BrandCode>
<Title>...</Title>
<Content>...</Content>
<ShowOnWebsite>false</ShowOnWebsite>
</Item>
</Items>
代码
这是我写的实现。 getItems
产生子节点而不立即将 xml 加载到内存中。
function getItems($fileName) {
if ($file = fopen($fileName, "r")) {
$buffer = "";
$active = false;
while(!feof($file)) {
$line = fgets($file);
$line = trim(str_replace(["\r", "\n"], "", $line));
if($line == "<Item>") {
$buffer .= $line;
$active = true;
} elseif($line == "</Item>") {
$buffer .= $line;
$active = false;
yield new SimpleXMLElement($buffer);
$buffer = "";
} elseif($active == true) {
$buffer .= $line;
}
}
fclose($file);
}
}
$output = new SimpleXMLElement('<?xml version="1.0" encoding="utf-8"?><Items></Items>');
foreach(getItems("test.xml") as $element)
{
if($element->ShowOnWebsite == "true") {
$item = $output->addChild('Item');
$item->addChild('Barcode', (string) $element->Barcode);
$item->addChild('BrandCode', (string) $element->BrandCode);
$item->addChild('Title', (string) $element->Title);
$item->addChild('Content', (string) $element->Content);
$item->addChild('ShowOnWebsite', $element->ShowOnWebsite);
}
}
$fileName = __DIR__ . "/test_" . rand(100, 999999) . ".xml";
$output->asXML($fileName);
输出
<?xml version="1.0" encoding="utf-8"?>
<Items><Item><Barcode>...</Barcode><BrandCode>...</BrandCode><Title>...</Title><Content>...</Content><ShowOnWebsite>true</ShowOnWebsite></Item></Items>
XMLReader
有一个 expand()
方法,但是 XMLWriter
缺少对应的方法。所以我在FluentDOM.
XMLWriter::collapse()
方法
这允许使用 XMLReader 读取 XML,将其扩展为 DOM,使用 DOM 方法将其 filter/manipulate 并写入返回 XML作者:
require __DIR__.'/../../vendor/autoload.php';
// Create the target writer and add the root element
$writer = new \FluentDOM\XMLWriter();
$writer->openUri('php://stdout');
$writer->setIndent(2);
$writer->startDocument();
$writer->startElement('Items');
// load the source into a reader
$reader = new \FluentDOM\XMLReader();
$reader->open(getXMLAsURI());
// iterate the Item elements - the iterator expands them into a DOM node
foreach (new FluentDOM\XMLReader\SiblingIterator($reader, 'Item') as $item) {
/** @var \FluentDOM\DOM\Element $item */
// only "ShowOnWebsite = true"
if ($item('ShowOnWebsite = "true"')) {
// write expanded node to the output
$writer->collapse($item);
}
}
$writer->endElement();
$writer->endDocument();
function getXMLAsURI() {
$xml = <<<'XML'
<Items>
<Item>
<Barcode>...</Barcode>
<BrandCode>...</BrandCode>
<Title>...</Title>
<Content>...</Content>
<ShowOnWebsite>false</ShowOnWebsite>
</Item>
<Item>
<Barcode>...</Barcode>
<BrandCode>...</BrandCode>
<Title>...</Title>
<Content>...</Content>
<ShowOnWebsite>true</ShowOnWebsite>
</Item>
<Item>
<Barcode>...</Barcode>
<BrandCode>...</BrandCode>
<Title>...</Title>
<Content>...</Content>
<ShowOnWebsite>false</ShowOnWebsite>
</Item>
</Items>
XML;
return 'data://text/plain;base64,'.base64_encode($xml);
}