使用 XMLreader 读取和解析大型 XML 文件。空值问题

Posted

技术标签:

【中文标题】使用 XMLreader 读取和解析大型 XML 文件。空值问题【英文标题】:Using XMLreader to read and parse large XML files. Empty values problem 【发布时间】:2019-07-14 18:59:20 【问题描述】:

我需要读取大约 1 GB 大小的 XML 文件。我的 XML:

<products>
<product>
<categoryName>Kable i konwertery AV</categoryName>
<brandName>Belkin</brandName>
<productCode>AV10176bt1M-BLK</productCode>
<productId>5616488</productId>
<productFullName>Kabel Belkin Kabel HDMI Ultra HD High Speed 1m-AV10176bt1M-BLK</productFullName>
<productEan>0745883767465</productEan>
<productEuroPriceNetto>59.71</productEuroPriceNetto>
<productFrontendPriceNetto>258.54</productFrontendPriceNetto>
<productFastestSupplierQuantity>23</productFastestSupplierQuantity>
<deliveryEstimatedDays>2</deliveryEstimatedDays>
</product>
<product>
<categoryName>Telewizory</categoryName>
<brandName>Sony</brandName>
<productCode>KDL32WD757SAEP</productCode>
<productId>1005662</productId>
<productFullName>Telewizor Sony KDL-32WD757 SAEP</productFullName>
<productEan></productEan>
<productEuroPriceNetto>412.33</productEuroPriceNetto>
<productFrontendPriceNetto>1785.38</productFrontendPriceNetto>
<productFastestSupplierQuantity>11</productFastestSupplierQuantity>
<deliveryEstimatedDays>6</deliveryEstimatedDays>
</product>
<product>
<categoryName>Kuchnie i akcesoria</categoryName>
<brandName>Brimarex</brandName>
<productCode>1566287</productCode>
<productId>885156</productId>
<productFullName>Brimarex Drewniane owoce, Kiwi - 1566287</productFullName>
<productEan></productEan>
<productEuroPriceNetto>0.7</productEuroPriceNetto>
<productFrontendPriceNetto>3.05</productFrontendPriceNetto>
<productFastestSupplierQuantity>7</productFastestSupplierQuantity>
<deliveryEstimatedDays>3</deliveryEstimatedDays>
</product>
</products>

我使用 XML 阅读器。

$reader = new XMLReader();
$reader->open($url);
$count = 0;

while($reader->read()) 
    if($reader->nodeType == XMLReader::ELEMENT)
        $nodeName = $reader->name;

    if(($reader->nodeType == XMLReader::TEXT || $reader->nodeType == XMLReader::CDATA)) 

        if ($nodeName == 'categoryName') $categoryName = $reader->value;
        if ($nodeName == 'brandName') $brandName = $reader->value;
        if ($nodeName == 'productCode') $productCode = $reader->value;
        if ($nodeName == 'productId') $productId = $reader->value;
        if ($nodeName == 'productFullName') $productFullName = $reader->value;
        if ($nodeName == 'productEan') $productEan = $reader->value;
        if ($nodeName == 'productEuroPriceNetto') $productEuroPriceNetto = $reader->value;
        if ($nodeName == 'productFastestSupplierQuantity') $productFastestSupplierQuantity = $reader->value;
        if ($nodeName == 'deliveryEstimatedDays') $deliveryEstimatedDays = $reader->value;
    

    if($reader->nodeType == XMLReader::END_ELEMENT && $reader->name == 'product') 
        $count++;
    

$reader->close();

除一个问题外,一切正常...当缺少某个值时,例如输出中的&lt;productEan&gt;&lt;/productEan&gt;,我从前一个非空标签中获取一个值,直到另一个非空标签。

例如,如果前一个节点类似于示例中的 &lt;productEan&gt;0745883767465&lt;/productEan&gt;,而另外两个 &lt;productEan&gt;&lt;/productEan&gt; 在输出数组中为空,我将得到相同的值,0745883767465

解决这个问题的正确方法是什么?或者也许有人有可行的解决方案......

【问题讨论】:

还值得一看 ***.com/questions/1835177/how-to-use-xmlreader-in-php,它展示了如何读取整个产品项目,然后您可以将其作为 SimpleXML 记录处理(所以 $node-&gt;productEan @Nick 建议的代码可以在最小的 xml 中正常工作。但是对于大型 XML,我会出现内存不足错误。所以现在有问题... 【参考方案1】:

这里有一些代码可以满足您的需求。它在遇到TEXTCDATA 节点时保存每个元素的值,然后在到达END_ELEMENT 时存储它。那时保存的值设置为'',因此如果没有找到元素的值,它会得到一个空字符串(如果您愿意,可以将其更改为null)。它还处理自闭合标签,例如 &lt;brandName /&gt;isEmptyElement 检查何时找到 ELEMENT 节点。它利用 PHP 变量变量来避免代码中的 if ($nodename == ...) 的长序列,而且还使用数组来存储每个产品的值,从长远来看,我认为这是解决您问题的更好方法。

$reader = new XMLReader();
$reader->xml($xml);
$count = 0;
$this_value = '';
$products = array();
while($reader->read()) 
    switch ($reader->nodeType) 
        case XMLReader::ELEMENT:
            // deal with self-closing tags e.g. <productEan />
            if ($reader->isEmptyElement) 
                $$reader->name = '';
                $products[$count][$reader->name] = '';
            
            break;
        case XMLReader::TEXT:
        case XMLReader::CDATA:
            // save the value for storage when we get to the end of the element
            $this_value = $reader->value;
            break;
        case XMLReader::END_ELEMENT:
            if ($reader->name == 'product') 
                $count++;
                print_r(array($categoryName, $brandName, $productCode, $productId, $productFullName, $productEan, $productEuroPriceNetto, $productFrontendPriceNetto, $productFastestSupplierQuantity, $deliveryEstimatedDays));
            
            elseif ($reader->name != 'products') 
                $$reader->name = $this_value;
                $products[$count][$reader->name] = $this_value;
                // set this_value to a blank string to allow for empty tags
                $this_value = '';
            
            break;
        case XMLReader::WHITESPACE:
        case XMLReader::SIGNIFICANT_WHITESPACE:
        default:
            // nothing to do
            break;
    

$reader->close();
print_r($products);

我省略了输出,因为它很长,但您可以在 demo on 3v4l.org 中看到运行中的代码。

【讨论】:

工作正常一段时间,突然我得到一个错误:允许的内存大小为 268435456 字节用尽(试图分配 20480 字节)在 ... 行 $$reader-&gt;name = $this_value;。我已将 php.ini 中的内存大小增加到 2048M 我尝试在我当前的 php 文件中设置它ini_set('memory_limit','2048M'); 但没有任何帮助...问题出在哪里? @K.B.听起来您的输入数据太大,因此您需要在循环内处理数据而不是存储它。因此,在if ($reader-&gt;name == 'product)` 块中,您应该对数据进行所有处理,然后(如果您使用的是数组),通过设置$products = array(); 将其丢弃 是的,此 XML 最大为 1 GB。在第一次尝试中,我在您的脚本之外使用了foreach($products as $product)。然后,我尝试按照您的建议将所有内容移至 if ($reader-&gt;name == 'product) 块,但这无济于事,或者我错过了一些东西。在我的本地服务器脚本上工作,但在远程服务器上不起作用。我可以给出这个 XML 的链接,也许你可以为这个问题提出解决方案... @K.B.所以这仍然在阅读循环中。当你改成在循环中处理数据的时候,是不是也去掉了$products这个数组? @K.B.很高兴听见。处理这么多数据肯定会很棘手。【参考方案2】:

如果您不使用单个值,而是将值存储在详细信息数组中,则可以在处理完每个元素后将数组清空...

$reader->open($url);
$count = 0;

$data = [];
while($reader->read()) 
    if($reader->nodeType == XMLReader::ELEMENT)
        $nodeName = $reader->name;

        if(($reader->nodeType == XMLReader::TEXT || $reader->nodeType == XMLReader::CDATA)) 
            $data[$nodeName] = $reader->value;
        

        if($reader->nodeType == XMLReader::END_ELEMENT && $reader->name == 'product') 
            // Process data
            echo ($data['productEan']??"Empty").PHP_EOL;
            // Reset
            $data = [];
            $count++;
        

$reader->close();

你的测试数据给出了...

0745883767465
Empty
Empty

【讨论】:

【参考方案3】:

重置每个循环上的所有变量。看起来如果你不给它赋值,它就会得到之前赋值的值。

<?php 
while($reader->read()) 
    $categoryName = 
    $brandName = 
    $productCode = 
    $productId = 
    $productFullName = 
    $productEan = 
    $productEuroPriceNetto = 
    $productFastestSupplierQuantity = 
    $deliveryEstimatedDays = '';
//... code

?>

【讨论】:

每个循环都重置变量的想法很好,但我卡住了......怎么做? 这是我的答案,在打开 while 后给它一个空值... hmmm 不工作,根本没有输出......也许我累了,我需要休息一下:),但我完全卡在这里

以上是关于使用 XMLreader 读取和解析大型 XML 文件。空值问题的主要内容,如果未能解决你的问题,请参考以下文章

使用 xmlReader 在 C# 中过滤特定元素值的大型 XML

使用 PHP 和 XMLReader 解析 XML

2 怎样解析XML文件或字符串

使用XDocument.Load(xmlreader)方法?

使用 XMLReader 解析大 XML 文件

在 C# 中使用 XmlReader 读取 Xml