Php 使用SimpleXML将多个XML文件转换为一个CSV

Php 使用SimpleXML将多个XML文件转换为一个CSV,php,xml,csv,simplexml,Php,Xml,Csv,Simplexml,我有一些xml文件,它们有相同的元素,但只有不同的信息 第一个文件test.xml <?xml version="1.0" encoding="UTF-8"?> <phones> <phone> <title>"Apple iPhone 5S"</title> <price> <regularprice>500</regularprice>

我有一些xml文件,它们有相同的元素,但只有不同的信息

第一个文件test.xml

<?xml version="1.0" encoding="UTF-8"?>
<phones>
    <phone>
        <title>"Apple iPhone 5S"</title>
        <price>
            <regularprice>500</regularprice>
            <saleprice>480</saleprice>
        </price> 
        <color>black</color>
    </phone>
</phones>
<?xml version="1.0" encoding="UTF-8"?>
<phones>
    <phone>
        <title>Nokia Lumia 830</title>
        <price>
            <regularprice>400</regularprice>
            <saleprice>370</saleprice>
        </price> 
        <color>black</color>
    </phone>
</phones>
正如您所见,我只设法将每个文件加载到一个变量中,对于每个文件,我必须编写if语句,这使得脚本太大,因此我想知道是否可以将所有文件加载到数组中,使用一个代码块处理它们,因为xml元素相同,并输出到一个.csv文件?本质上,我需要相同的test.csv输出,只需要更少的php代码


提前谢谢。

谢谢@Ghost为我指明了正确的方向。这就是我的解决方案

<?php

$filexml = array ('test.xml', 'test1.xml');


//Headers
$fp = fopen('file.csv', 'w');

$headers = array('title', 'color');
$converted_array = array_map("strtoupper", $headers);


fputcsv($fp, $converted_array, ',', '"');


//XML
foreach ($filexml as $file) {
    if (file_exists($file)) {
        $xml = simplexml_load_file($file);

        foreach ($xml->phone as $phone) {
        $values = array(
               "title" => (string)$phone->title = trim(str_replace ( "\"", "&quot;", $phone->title ), " "), 
               "color" => (string)$phone->color
            );
            fputcsv($fp, $values, ',', '"');
        }
        echo $file . ' converted to .csv sucessfully' . '<br>';
    } else {
        echo $file . ' was not found' . '<br>';
    }


}

fclose($fp);

?>

除了使用数组之外,PHP中还有更多可以使其更加简单的功能。就像数组可以表示文件列表一样,PHP中的其他结构也可以这样做

例如,由于您拥有的XML文件很可能位于特定目录中,并且其文件名遵循某种模式,因此可以使用全局迭代器来轻松表示这些文件:

$inputFiles = new GlobIterator(__DIR__ . '/*.xml');
foreach(extract_phones($inputFiles) as $phone) {
    # $phone is a SimpleXMLElement here
}
然后,您可以对它们进行
foreach
,稍后我将用另一个示例演示

这样的列表允许您简化处理过程。这很重要,因为许多程序都有某种通用公式:输入、处理和输出。这也被称为IPO或IPO+S模式。S代表存储。在您的情况下,当您处理输入数据时,您还将存储到一个新的CSV文件中,该文件也是输出(处理完成后)

当您遵循这样一个通用模型时,构建代码就更容易了,而使用更好的结构,您通常拥有更少的代码。即使不是这样,代码的每一部分都更加独立和更小,这通常是您所需要的

在回答开始时用GlobIterator显示的XML文件列表旁边,还有其他迭代器可以帮助处理XML数据

例如,您有1-n个XML文件,其中包含0-n
元素。您知道要处理这些
元素中的任何一个,您已经确切地知道要对它们执行什么操作(从中提取一些数据)。那么,首先在所有XML文件中列出所有
元素不是很好吗

这可以在PHP中借助生成器轻松完成。这是一个可以在“运行”时多次返回值的函数。这是一个简化,最好显示一些代码来说明这一点。假设我们已经得到了XML文件列表作为输入,并且希望从中提取所有
元素。当然,您可以创建所有这些
元素的数组,然后处理该数组。但是,生成器能够直接提供所有这些
元素,以便在
foreach
循环中使用:

function extract_phones(Traversable $files) {
    foreach ($files as $file) {
        $xml = simplexml_load_file($file);
        if ($xml === false) {
            continue;
        }
        foreach ($xml->phone as $phone) {
            yield $phone;
        }
    }
}
正如这个示例性的生成器函数所示,它遍历所有
$文件
,尝试将它们作为SimpleXMLElement加载,如果成功,则迭代所有
元素并生成它们

这意味着,如果在
foreach
中调用函数
extract\u phones
,则该循环的每个
元素都将为simplexmlement

$inputFiles = new GlobIterator(__DIR__ . '/*.xml');
foreach(extract_phones($inputFiles) as $phone) {
    # $phone is a SimpleXMLElement here
}
现在,您的问题是创建CSV文件作为输出。可以创建一个SplFileObject来传递输出,并在处理时访问它。它的工作原理基本上与在问题中传递文件句柄的工作原理相同,但它具有更好的语义,允许以后更轻松地更改代码(您可以将其替换为另一个行为相同的对象)

此外,我在代码中看到了一些细节,值得先讨论一下。您正在将引号编码为HTML实体:

 trim(str_replace( "\"", "&quot;", $phone->title ), " ")
您这样做很可能是因为您希望在CSV文件中包含HTML实体。但是,CSV文件不需要这样做。您还希望CSV文件中的数据尽可能通用。转换文件格式时,CSV文件是稍后在HTML上下文中使用还是在电子表格应用程序中使用不应引起您的关注。我的建议是把这件事撇开,到另一个地方处理。这更多内容属于的地方,这是以后的地方,例如,如果您使用CSV中的数据创建一些HTML

这样可以保持转换和数据的干净,还可以删除处理过程中的详细位置,这不仅会使代码更加复杂,而且常常会在程序中引入缺陷

就我个人而言,我将从我的示例中删除它

让我们把这些放在一起:从所有XML文件中获取所有手机,并将感兴趣的字段存储到输出CSV文件中:

$files  = new GlobIterator(__DIR__ . '/*.xml');
$phones = extract_phones($files);

$output = new SplFileObject('file.csv', 'w');
$output->fputcsv($header = ["title", "color"]);

foreach ($phones as $phone) {
    $output->fputcsv(
        [
            $phone->title,
            $phone->color,
        ]
    );
}
然后创建您要查找的输出文件(不带HTML实体):

所有这些需要的是我上面已经展示过的生成器函数,它本身也有直接的代码。其他一切都已经随PHP一起提供了。下面是完整的示例代码:

<?php
/**
 * @link http://stackoverflow.com/questions/26074850/convert-multiple-xml-files-to-csv-with-simplexml
 */

function extract_phones(Traversable $files)
{
    foreach ($files as $file) {
        $xml = simplexml_load_file($file);
        if ($xml === false) {
            continue;
        }
        foreach ($xml->phone as $phone) {
            yield $phone;
        }
    }
}

$files  = new GlobIterator(__DIR__ . '/*.xml');
$phones = extract_phones($files);

$output = new SplFileObject('file.csv', 'w');
$output->fputcsv($header = ["title", "color"]);

foreach ($phones as $phone) {
    $output->fputcsv(
        [
            $phone->title,
            $phone->color,
        ]
    );
}

echo file_get_contents($output->getFilename());

是的,你可以这样做,把文件名放在一个数组中,每个文件名循环一次,把一个容器放在一个数组中保存所有信息,最后,fputcsv他们全部感谢@hakre你的回答并清除了所有信息。这是非常有用的。你能解释这一点真是太好了,但是当你真正需要的只是在文件名上的另一个循环时,设计得有点过头了。@DanMan:实际上我的目标是降低代码的复杂性。这通常与过度工程相反。
<?php
/**
 * @link http://stackoverflow.com/questions/26074850/convert-multiple-xml-files-to-csv-with-simplexml
 */

function extract_phones(Traversable $files)
{
    foreach ($files as $file) {
        $xml = simplexml_load_file($file);
        if ($xml === false) {
            continue;
        }
        foreach ($xml->phone as $phone) {
            yield $phone;
        }
    }
}

$files  = new GlobIterator(__DIR__ . '/*.xml');
$phones = extract_phones($files);

$output = new SplFileObject('file.csv', 'w');
$output->fputcsv($header = ["title", "color"]);

foreach ($phones as $phone) {
    $output->fputcsv(
        [
            $phone->title,
            $phone->color,
        ]
    );
}

echo file_get_contents($output->getFilename());