PHP CSV to XML, как работать со строками, разделенными каналом

1

Во-первых, я знаю, что это довольно длинный/подробный пост, если вы ищете суть моей проблемы, вы можете прыгнуть на дно, где у меня есть TL;DR. Спасибо заранее всем комментаторам

Я работаю над функцией для веб-сайта моих клиентов. У них более старая версия Microsoft Excel на MAC, которая не поддерживает.XML - в системе хранения, которую они используют.XML

Поэтому мне нужно закодировать возможность преобразования CSV в XML, но XML должен соответствовать структуре, требуемой компонентом store. Я уже закодировал функцию XML для CSV, которая работает.

Это XML-результат системы хранилища (я удалил значения для безопасности моих клиентов-клиентов):

<orders>
  <order>
    <order_id>38</order_id>
    <order_number>000015</order_number>
    <order_status>Authorized</order_status>
    <order_date>0000-00-00 00:00:00</order_date>
    <customer_email>[email protected]</customer_email>
    <order_amount>order total</order_amount>
    <base_order_amount>pre shipping order total</base_order_amount>
    <shipping_type>Basic Shipping</shipping_type>
    <shipping_price> $0.00</shipping_price>
    <billing_first_name>Name</billing_first_name>
    <billing_last_name>B</billing_last_name>
    <billing_address1>PO / Add</billing_address1>
    <billing_address2></billing_address2>
    <billing_city>Town</billing_city>
    <billing_state_province>province</billing_state_province>
    <billing_country>Canada</billing_country>
    <billing_postal_code>postal code</billing_postal_code>
    <billing_phone></billing_phone>
    <emt_quest>test</emt_quest>
    <emt_answ>test</emt_answ>
    <emt_answ_conf>test</emt_answ_conf>
    <shipping_first_name>Name</shipping_first_name>
    <shipping_last_name>B</shipping_last_name>
    <shipping_address1>PO / Add</shipping_address1>
    <shipping_address2></shipping_address2>
    <shipping_city>Town</shipping_city>
    <shipping_state_province>province</shipping_state_province>
    <shipping_country>Canada</shipping_country>
    <shipping_postal_code>postal code</shipping_postal_code>
    <shipping_phone></shipping_phone>
    <items>
      <item>
        <item_name>Sample Item</item_name>
        <item_price>$8.00</item_price>
        <item_quantity>12</item_quantity>
      </item>
      <item>
        <item_name>Sample Item 2</item_name>
        <item_price>$12.00</item_price>
        <item_quantity>12</item_quantity>
      </item>
    </items>
  </order>

Это код моей функции XML для CSV

<?php

function xml2csv($xmlFile, $xPath) {
    $csvData = "";
    // Load the XML file
    $xml = simplexml_load_file($xmlFile);

    // xpath to search
    $path = $xml->order;

    //get headers (xpath must match above)
    $headers = get_object_vars($xml->order[0]);

    // Loop through the first row to get headers
    foreach($headers as $key => $value){
        $csvData .= $key . ',';
    }
            // Trim off the extra comma
        $csvData = trim($csvData, ',');

        // Add an LF
        $csvData .= "\n";
    foreach($path as $item) {

        // Loop through the elements in specificed xpath

        foreach($item as $key => $value) {

            //check for a second generation children of specified first generation child
            if ($key == "items") {
                $itemString = "";
                // if first generation child has children then loop through each second gen child
                foreach ($item->children() as $child) {
                    // loop through each xpath of second generation child
                    foreach($child as $value) {
                        // for value of each xpath of second generation child get value as out
                        foreach($value->children() as $out) {
                            //combine each value into itemString for export to .csv
                            $itemString .= $out . "|";
                            }
                        }
                    }
                    // place item string in csvData string and remove extra pipe
                    $csvData .= trim($itemString, "|");
                }
            //else put xpath values of first geneartion child in .csv
            else {

            $csvData .=  trim($value) . ',';
            }

        }
        // Trim off the extra comma
        $csvData = trim($csvData, ',');

        // Add an LF
        $csvData .= "\n";

    }

    // Return the CSV data
    return $csvData;

} 

При вызове с заданным.XML файлом из системы хранилища он выводит следующий.CSV файл (я использовал фиктивные значения "цена товара" не случайно)

order_id,order_number,order_status,order_date,customer_email,order_amount,base_order_amount,shipping_type,shipping_price,billing_first_name,billing_last_name,billing_address1,billing_address2,billing_city,billing_state_province,billing_country,billing_postal_code,billing_phone,emt_quest,emt_answ,emt_answ_conf,medicinal_use,shipping_first_name,shipping_last_name,shipping_address1,shipping_address2,shipping_city,shipping_state_province,shipping_country,shipping_postal_code,shipping_phone,items
00,000000,Authorized,0000-00-00 00:00:00,[email protected],$00.00,$00.00,Basic Shipping,$0.00,Me,Initial,123 Some Person Street,,Personville,Prov/State,Country,postal,,test,test,test,test,test,test,test,,test,test,test,test,,item name|item price|item quantity
01,000000,Authorized,0000-00-00 00:00:00,[email protected],$00.00,$00.00,Basic Shipping,$0.00,Me,Initial,123 Some Person Street,,Personville,Prov/State,Country,postal,,test,test,test,test,test,test,test,,test,test,test,test,,item name|item price|item quantity
02,000000,Authorized,0000-00-00 00:00:00,[email protected],$00.00,$00.00,Basic Shipping,$0.00,Me,Initial,123 Some Person Street,,Personville,Prov/State,Country,postal,,test,test,test,test,test,test,test,,test,test,test,test,,item name|item price|item quantity
03,000000,Authorized,0000-00-00 00:00:00,[email protected],$00.00,$00.00,Basic Shipping,$0.00,Me,Initial,123 Some Person Street,,Personville,Prov/State,Country,postal,,test,test,test,test,test,test,test,,test,test,test,test,,item name|item price|item quantity
04,000000,Authorized,0000-00-00 00:00:00,[email protected],$00.00,$00.00,Basic Shipping,$0.00,Me,Initial,123 Some Person Street,,Personville,Prov/State,Country,postal,,test,test,test,test,test,test,test,,test,test,test,test,,item name|item price|item quantity|item name|item price|item quantity

Целью здесь является то, что мой клиент может загрузить.CSV непосредственно из системы хранилища (а не по умолчанию.XML) - справиться с этим в Excel, поскольку они должны обрабатывать их заказы, а затем загрузить этот.CSV обратно в магазин - где он автоматически преобразуется в XML, как показано выше.

Поскольку.CSV - это плоский формат, то, что я сделал, конденсировал элементы XML в простую строку.CSV, где каждое значение делится на | которые не будут использоваться ни в одном из наших разметки на сайте. Как такое item name|item price|item quantity

Вот мой код, который пытается достичь этого, я приближаюсь, но у меня есть некоторое неудобное поведение с выходом. Он выдает неопределенную ошибку на отмеченной строке $itemvalue = $doc->createTextNode($irow[$g]); (как будто цикл работает слишком много раз), а также не дает ожидаемого результата.

function contains($substring, $string) {
        $pos = strpos($string, $substring);

        if($pos === false) {
                // string needle NOT found in haystack
                return false;
        }
        else {
                // string needle found in haystack
                return true;
        }

}

function csv2xml($csvData) {
    $outputFilename   = 'test.xml';
    // Open csv to read
    $input  = fopen($csvData, 'rt');

    // Get the headers of the file
    $headers = fgetcsv($input);

    // Create a new dom document with pretty formatting
    $doc  = new DomDocument();
    $doc->formatOutput   = true;

    // Add a root node to the document
    $root = $doc->createElement('orders');
    $root = $doc->appendChild($root);

    while (($row = fgetcsv($input)) !== FALSE) {

        $container = $doc->createElement('order');

 foreach ($headers as $i => $header)
 {
      //set temp file name here
     $tempFile = "temp.csv";

     //prepare mockCSV
    $mockCSV = "";
    $mockCSV .= "item_name,item_price,item_quantity";
    $mockCSV .= "\n";

    //check if current property has items data with |
     if (contains("|", $row[$i])) {
         //if it does create array of data
          $item_arr = explode("|", $row[$i]);

          //create header for 'items' node
          $child = $doc->createElement($header);
          $child = $container->appendChild($child);

          //count for items
          $count = 0;
          foreach($item_arr as $k => $item) {
              $mockCSV .= trim($item) . ",";
              if($count == 2) {
                            // Trim off the extra comma
                $mockCSV = trim($mockCSV, ',');

                // Add an LF
                $mockCSV .= "\n";
                }
                $count++;
              }
                                        // Trim off the extra comma
                $mockCSV = trim($mockCSV, ',');

                // Add an LF
                $mockCSV .= "\n";

                //put mock CSV data in temp file
                $f = fopen($tempFile, "w");
                fwrite($f, $mockCSV);
                fclose($f);

                //get data from temp file
                $iteminput = fopen($tempFile, 'rt');
                //get headers from temp file
                $itemheaders = fgetcsv($iteminput);

                    while (($irow = fgetcsv($iteminput)) !== FALSE) {
                                                    $itemchild = $doc->createElement('item');
                        foreach($itemheaders as $g => $itemheader) {
          $subchild = $doc->createElement($itemheader);
          $subchild = $itemchild->appendChild($subchild);
          $itemvalue = $doc->createTextNode($irow[$g]);  /* OFFSET HAPPENS HERE */
          $itemvalue = $subchild->appendChild($itemvalue);
                        }
                    }
                                $itemchild = $child->appendChild($itemchild);

         }

    else {
          $child = $doc->createElement($header);
          $child = $container->appendChild($child);
          $value = $doc->createTextNode($row[$i]);
          $value = $child->appendChild($value);
        } 
 }

        $root->appendChild($container);
    }

    $strxml = $doc->saveXML();
$handle = fopen($outputFilename, "w");
fwrite($handle, $strxml);
fclose($handle);

}

echo csv2xml("test.csv");

?>

Ожидаемый результат должен быть таким же, как и структура XML, опубликованная выше, но вместо этого она делает следующее:

<orders>
  <order>
    <order_id>38</order_id>
    <order_number>000015</order_number>
    <order_status>Authorized</order_status>
    <order_date>0000-00-00 00:00:00</order_date>
    <customer_email>[email protected]</customer_email>
    <order_amount>$96.00</order_amount>
    <base_order_amount>$96.00</base_order_amount>
    <shipping_type>Basic Shipping</shipping_type>
    <shipping_price> $0.00</shipping_price>
    <billing_first_name>Name</billing_first_name>
    <billing_last_name>B</billing_last_name>
    <billing_address1>PO / Add</billing_address1>
    <billing_address2></billing_address2>
    <billing_city>Town</billing_city>
    <billing_state_province>province</billing_state_province>
    <billing_country>Canada</billing_country>
    <billing_postal_code>postal code</billing_postal_code>
    <billing_phone></billing_phone>
    <emt_quest>test</emt_quest>
    <emt_answ>test</emt_answ>
    <emt_answ_conf>test</emt_answ_conf>
    <shipping_first_name>Name</shipping_first_name>
    <shipping_last_name>B</shipping_last_name>
    <shipping_address1>PO / Add</shipping_address1>
    <shipping_address2></shipping_address2>
    <shipping_city>Town</shipping_city>
    <shipping_state_province>province</shipping_state_province>
    <shipping_country>Canada</shipping_country>
    <shipping_postal_code>postal code</shipping_postal_code>
    <shipping_phone></shipping_phone>
    <items>
      <item>
        <item_name></item_name>
        <item_price></item_price>
        <item_quantity></item_quantity>
      </item>
    </items>
  </order>

И не вставлять значения для некоторых полей. Также он не повторяется для двойных записей продукта, как показано, чье исходное поле.CSV выглядит как это item name|item price|item quantity|item name|item price|item quantity

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

Любая помощь очень ценится, на данный момент я думаю, что это что-то простое, и мне просто нужно еще пару глаз на эту тему.

Более того, я использую очень неоднородный код здесь, я чувствую, что я не в курсе.PHP - я чувствую, что должна быть какая-то логическая проблема с тем, как я это делаю - мой способ может работать, но должно быть более упорядоченный метод. Если кто-нибудь скажет мне, что это такое, - тот ответ, который я действительно ищу.

TL: DR начинается здесь. Я пытаюсь преобразовать данные.CSV в структурированные данные.XML, используя разграничение каналов для детей поколения второго поколения и третьего поколения

Только одно поле в моем исходном элементе.CSV "элементы" содержит такую информацию - все остальные элементы представляют собой одиночную запись с одним ключом, данные похожи на это item name|item price|item quantity|item name|item price|item quantity

Так что я делаю проверку на | внутри строки.CSV, которая в настоящее время проходит через цикл, и если она обнаружена, я использую explode() для создания массива того, что там было.

Я пробовал воссоздать mock CSV файл и помещать его в каталог temp для размещения этой информации, а затем использовать базовый CSV для XML, который работает в моей программе, чтобы поместить эти данные в документ XML Dom

Ожидаемый результат:

<items>
  <item>
    <item_name>Sample Item</item_name>
    <item_price>$8.00</item_price>
    <item_quantity>12</item_quantity>
  </item>
  <item>
    <item_name>Sample Item 2</item_name>
    <item_price>$8.00</item_price>
    <item_quantity>12</item_quantity>
  </item>
</items>

Результат, который я получаю:

<items>
  <item>
    <item_name></item_name>
    <item_price></item_price>
    <item_quantity></item_quantity>
  </item>
</items>

Много информации, которую мне нужно найти, чтобы правильно проиллюстрировать проблему, но моя проблема проста - как я могу добиться результата, который я хочу.

Теги:
csv
excel

1 ответ

1
Лучший ответ

Позвольте мне сначала создать резервную копию и предложить подпрограмму для CSV для XML, а затем позаботиться о элементах, передаваемых по каналам.

Некоторые комментарии:

  • Я предпочитаю SimpleXML через DOM для его простоты использования, поэтому я буду использовать его в этом примере. Конечно, это можно сделать и с DOM.
  • Я буду использовать str_getcsv() вместо fgetcsv(), чтобы создать рабочий пример в Интернете.

базовый CSV для XML

// XML: set up object
$xml = simplexml_load_string("<orders/>");

// CSV: assume CSV in $c, get it as a whole
$csv = str_getcsv($c, "\n");

// CSV: separate 1st row with field names from the following rows
$names = str_getcsv(array_shift($csv));

// CSV: parse row by row
foreach ($csv as $row) {

    // CSV: combine names as keys => data as values
    $row = array_combine($names, str_getcsv($row));

    // XML: create new <order>
    $xml_order = $xml->addChild("order");

    // CSV: parse a single row
    foreach ($row as $key => $value) {

        // *****
        // XML: create field as child of <order>
        $xml_order->addChild($key, $value);
        // *****
    }
}

обрабатывать элементы трубопровода

следующий код заменяет строки между //***** выше

// CSV: check for pipes, attention use strict comparison ===
if (strpos($value, "|") === false) {

    // XML: no pipe, create node as a child of <order>
    $xml_order->addChild($key, $value);

} else {

    // CSV: pipe present, split up data
    $csv_items = str_getcsv($value,"|");

    // XML: create <items> node     
    $xml_items = $xml_order->addChild($key);

    // CSV: iterate over $csv_items, each 3 elements = 1 row
    // chop row after row 
    while (!empty($csv_items)) {

        // XML: create <item> node as child of <items>
        $xml_item = $xml_items->addChild("item");

        // XML: create children of <item> node
        $xml_item->addChild("item_name", array_shift($csv_items));
        $xml_item->addChild("item_price", array_shift($csv_items));
        $xml_item->addChild("item_quantity", array_shift($csv_items));
    }
}

комбинировать код без комментариев

$xml = simplexml_load_string("<orders/>");
$csv = str_getcsv($c, "\n"); // assume CSV in $c
$names = str_getcsv(array_shift($csv));

foreach ($csv as $row) {
    $row = array_combine($names, str_getcsv($row));
    $xml_order = $xml->addChild("order");

    foreach ($row as $key => $value) {

        if (strpos($value, "|") === false) 
            $xml_order->addChild($key, $value);
        else {
            $csv_items = str_getcsv($value,"|");
            $xml_items = $xml_order->addChild($key);

            while (!empty($csv_items)) {
                $xml_item = $xml_items->addChild("item");
                $xml_item->addChild("item_name", array_shift($csv_items));
                $xml_item->addChild("item_price", array_shift($csv_items));
                $xml_item->addChild("item_quantity", array_shift($csv_items));
            }
        }
    }
}

см. его работу: https://eval.in/368945

  • 0
    Спасибо за это, я сделаю тест с живыми данными завтра поздно, где я сейчас, я прокомментирую позже. Похоже на твердую реализацию. Я рассматривал двойные разделители, чтобы разделить массив дважды, но ваш метод намного чище. Еще раз спасибо.
  • 0
    Ну, я получил второе дыхание после кофе и пошел об этом сейчас, работает как шарм =). Спасибо брат! Теперь, когда у меня есть рабочие скрипты csv2xml и xml2csv, я должен приступить к их внедрению в систему управления контентом - fun: \

Ещё вопросы

Сообщество Overcoder
Наверх
Меню