Использование XMLreader для чтения и анализа больших файлов XML.Проблема пустых значений - PullRequest
0 голосов
/ 21 февраля 2019

Мне нужно прочитать XML-файлы размером около 1 ГБ.Мой 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.

$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();

Все работает нормально, кроме одной проблемы ... Когда пропущено какое-то значение, например <productEan></productEan> в выводе Iполучаю значение из предыдущего, не пустого тега, пока другой тег, который не является пустым.

Например, если предыдущий узел такой же, как в примере <productEan>0745883767465</productEan>, и еще два <productEan></productEan> пусты в выходном массиве Iполучая то же значение, 0745883767465.

Как правильно решить эту проблему?Или, может быть, у кого-то есть рабочее решение ...

Ответы [ 3 ]

0 голосов
/ 21 февраля 2019

Вот код, который будет делать то, что вы хотите.Он сохраняет значение для каждого элемента, когда встречается с узлом TEXT или CDATA, а затем сохраняет его, когда достигает значения END_ELEMENT.В то время сохраненное значение устанавливается на '', поэтому, если для элемента не найдено значение, оно получает пустую строку (это можно изменить на null, если вы предпочитаете).Он также имеет дело с самозакрывающимися тегами, например <brandName /> с проверкой 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);

Я опустил вывод, так как он довольно длинный, но вы можете увидеть работающий код в этой демонстрации на 3v4l.org .

0 голосов
/ 21 февраля 2019

Если вместо использования отдельных значений вы сохраняете значения в массиве деталей, вы можете очистить массив после обработки каждого элемента ...

$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
0 голосов
/ 21 февраля 2019

Сброс всех переменных в каждом цикле.Кажется, что если вы не назначаете ему никакого значения, оно получает предыдущее назначенное значение.

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