【问题标题】:PHP CSV to XML how to deal with pipe delimited stringsPHP CSV to XML 如何处理管道分隔的字符串
【发布时间】:2015-05-22 17:37:47
【问题描述】:

首先,我知道这是一篇相当长/详细的帖子,如果您正在寻找我的问题的要点,您可以跳到我有 TLDR 的底部。在此先感谢所有评论者

我一直在为我的客户网站开发一项功能。他们在 MAC 上有旧版本的 Microsoft Excel,不支持 .XML - 他们的商店系统使用 .XML

所以我需要编写将 CSV 转换为 XML 的能力,但 XML 必须符合存储组件所需的结构。我已经编写了一个可以工作的 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>test@someemail.ca</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,i@me.ca,$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,i@me.ca,$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,i@me.ca,$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,i@me.ca,$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,i@me.ca,$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-&gt;createTextNode($irow[$g]); 上引发未定义的 offet 错误(好像循环运行了太多次)并且也不会产生预期的输出。

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>test@someemail.ca</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

这是我的问题,我似乎无法正确处理管道分隔字段,它没有按预期输出。在早期版本的代码中,我得到了所有数据,但它没有创建单独的“项目”节点。

非常感谢任何帮助,在这一点上,我认为这很简单,我只需要另一双眼睛关注这个主题。

更重要的是,我在这里使用的代码非常不完整必须是更精简的方法。如果有人能告诉我那是什么 - 这就是我真正想要的答案。

TL:DR 从这里开始 我正在尝试使用管道分隔的第二代和第三代 XML 子代将 .CSV 数据转换为结构化的 .XML 数据

我的源 .CSV 文件“项目”中只有一个字段包含此类信息 - 所有其他项目都是单键单项数据看起来像这样 item name|item price|item quantity|item name|item price|item quantity

所以我要做的是检查 |在当前通过循环运行的 .CSV 字符串内部,如果检测到它,我使用 explode() 创建一个包含其中内容的数组。

我尝试重新创建一个模拟 CSV 文件并将其放在临时目录中以放置此信息,然后使用基本的 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>

我需要大量信息才能正确说明问题,但我的问题很简单 - 我怎样才能实现我想要的输出。

【问题讨论】:

    标签: php xml excel csv


    【解决方案1】:

    让我先备份并提供一个 CSV 到 XML 的例程,然后处理管道元素。

    一些cmets:

    • 我更喜欢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));
        }
    }
    

    组合代码不带 cmets

    $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

    【讨论】:

    • 感谢您的支持,明天我将使用实时数据对其进行测试,现在已经很晚了,稍后我会回复。似乎是一个可靠的实现。我正在考虑使用双定界符,因此我可以将数组拆分两次,但您的方法要干净得多。再次感谢。
    • 嗯,喝完咖啡后我又来了一次,现在就去做,就像一个魅力 =)。多谢兄弟!既然我有工作的 csv2xml 和 xml2csv 脚本,我必须着手将它们实施到内容管理系统中 - 有趣:\
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-02-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-09-14
    • 2021-12-11
    相关资源
    最近更新 更多