如何根据 XML 节点中的记录在 PHP 中编辑大型 XML 文件

How to edit large XML files in PHP based on a record in the XML Node

提问人:Lorenzo 提问时间:11/15/2021 更新时间:11/16/2021 访问量:172

问:

我正在尝试通过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”为 true 的记录。

我遇到的问题由于 XML 非常大,因此简单的解决方案(例如使用 SimpleXML 或将 XML 加载到正文中并编辑其中的节点)不起作用。因为它们都将整个文件读入内存,速度太慢,通常会失败。

我还研究了 prewk/xml-string-streamer (https://github.com/prewk/xml-string-streamer),它非常适合流式传输大型 XML 文件,因为它不会将它们放在内存中,尽管我找不到任何通过该解决方案修改 XML 的方法。(其他在线帖子说你需要在内存中拥有节点才能编辑它们)。

有人知道如何解决这个问题吗?

php dom xml 解析

评论


答:

1赞 Douma 11/16/2021 #1

目标

期望的结果:我想创建一个新的 XML 文件,其中仅包含子项“ShowOnWebsite”为 true 的记录。

鉴于

测试 .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 加载到内存中。getItems

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>
0赞 ThW 11/16/2021 #2

XMLReader有一个方法,但缺少对应的。所以我在 FluentDOM 中添加了一个方法。expand()XMLWriterXMLWriter::collapse()

这允许使用 XMLReader 读取 XML,将其扩展为 DOM,使用 DOM 方法过滤/操作它并使用 XMLWriter 将其写回:

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);
}