使用PHP将XML转换为CSV但有所不同

时间:2018-07-01 19:54:15

标签: php xml csv simplexml

我正在尝试使用PHP SimpleXML类将某些XML文件转换为CSV。但是,我无法实现所需的结果,因为一个父级可能有多个具有相同名称的子元素。我当前的XML文件如下:

<?xml version="1.0" encoding="UTF-8"?>

<root>
    <club>
        <name>Green Riders</name>
        <membership>Free</membership>
        <boardMember>
            <name>James F.</name>
            <position>CEO</position>
        </boardMember>
        <boardMember>
            <name>Helen D.</name>
            <position>Associate Director</position>
        </boardMember>
    </club>
    <club>
        <name>Broken Dice</name>
        <membership>Paid</membership>
        <boardMember>
            <name>Patrick B.</name>
            <position>CEO</position>
        </boardMember>
    </club>    
</root>

我希望实现的CSV输出如下:

club,name,membership,boardMember>Name,boardMember>position
Green Riders,Free,James F.,CEO
Green Riders,Free,Helen D., Associate Director
Broken Dice,Paid,Patrick B., CEO

在没有将元素名称硬编码到脚本中的情况下(是否可以在任何通用XML文件上使用),是否有实现此目标的方法?

由于我将拥有超过25种XML变体,所以我真的希望这是可能的。因此为每个脚本编写专用脚本确实效率不高。 谢谢!

2 个答案:

答案 0 :(得分:1)

由于每个子节点的数据都必须在csv中排成一列,包括根根数据,因此,您首先可以捕获并存储根数据,然后遍历子节点并打印其数据,并在它们之前加上根数据。

请检查以下代码:

$xml = simplexml_load_file("your_xml_file.xml") or die("Error: Cannot create object");

$csv_delimeter = ",";
$csv_new_line = "\n";

foreach($xml->children() as $n) {
    $club_data = array();
    $club_data[] = $n->name;
    $club_data[] = $n->membership;

    if (isset($n->boardMember)) {
        foreach ($n->boardMember as $boardMember) {
            $boardMember_data = $club_data;
            $boardMember_data[] = $boardMember->name;
            $boardMember_data[] = $boardMember->position;

            echo implode($csv_delimeter, $boardMember_data).$csv_new_line;
        }
    }
    else {
        echo implode($csv_delimeter, $club_data).$csv_new_line;
    }
}

在使用示例xml数据进行测试之后,它生成了以下类型的输出:

Green Riders,Free,James F.,CEO
Green Riders,Free,Helen D., Associate Director
Broken Dice,Paid,Patrick B., CEO

您可以根据自己的情况为以下项设置不同的值:

$csv_delimeter = ",";
$csv_new_line = "\n";

由于csv输出中没有严格的规则-像delimeter可以是“,”,“,”,“;”或“ |”并且新行也可以是“ \ n \ r”

代码可以即时打印出csv行,但是,如果要将csv数据保存在文件中,则不是一一写入行,更好的方法是创建整个数组并写入除非xml数据很大,否则它一次(因为磁盘访问成本很高)。您将在网上获得大量简单的php array-to-csv函数示例。

答案 1 :(得分:1)

这是不可能的。 XML是一个嵌套结构,您会错过信息。您可以为XML结构定义一些默认映射,但是这确实非常复杂。因此,手动定义映射要容易得多(耗时少)。

可重复使用的转换

function readXMLAsRecords(string $xml, array $map) {

  // load the xml
  $document = new DOMDocument();
  $document->loadXml($xml);
  $xpath = new DOMXpath($document);

  // iterate the elements defining the rows 
  foreach ($xpath->evaluate($map['row']) as $row) {
    $line = [];
    // get the field values from the current $row
    foreach ($map['columns'] as $name => $expression) {
      $line[$name] = $xpath->evaluate($expression, $row);
    }
    // return a line
    yield $line;
  }
}

映射

使用DOMXpath::evaluate()的Xpath表达式可以返回字符串。因此,我们需要一个返回boardMember节点的表达式以及这些字段的表达式列表。

$map = [
  'row' => '/root/club/boardMember',
  'columns' => [
    'club_name' => 'string(parent::club/name)',
    'club_membership' => 'string(parent::club/membership)',
    'board_member_name' => 'string(name)',
    'board_member_position' => 'string(position)'
  ]
];

至CSV

readXMLAsRecords()返回一个生成器,您可以在其上使用foreach

$csv = fopen('php://stdout', 'w');
fputcsv($csv, array_keys($map['columns']));
foreach (readXMLAsRecords($xml, $map) as $record) {
  fputcsv($csv, $record);
}

输出:

club_name,club_membership,board_member_name,board_member_position
"Green Riders",Free,"James F.",CEO
"Green Riders",Free,"Helen D.","Associate Director"
"Broken Dice",Paid,"Patrick B.",CEO