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


Convert multiple XML files to one CSV with SimpleXML

我有一些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>

第二个文件test1.xml

<?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>

我需要将这些xml文件中的一些值转换为1个test.csv文件

所以我使用这个php代码

<?php
$filexml1='test.xml';
$filexml2='test1.xml';
    //File 1
    if (file_exists($filexml1)) {
        $xml = simplexml_load_file($filexml1); 
        $f = fopen('test.csv', 'w');
    $headers = array('title', 'color');
    $converted_array = array_map("strtoupper", $headers);

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

    foreach ($xml->phone as $phone) {
        //$phone->title = trim($phone->title, " ");
        // Array of just the components you need...
        $values = array(
           "title" => (string)$phone->title = trim(str_replace ( "'"", "&quot;", $phone->title ), " "), 
           "color" => (string)$phone->color
        );
        fputcsv($f, $values,',','"');
    }
    fclose($f); 
    echo "<p>File 1 coverted to .csv sucessfully</p>";
} else {
    exit('Failed to open test.xml.');
}
    //File 2
    if (file_exists($filexml2)) {
        $xml = simplexml_load_file($filexml2); 
        $f = fopen('test.csv', 'a');

    //the same code for second file like for the first file
    echo "<p>File 2 coverted to .csv sucessfully</p>";
} else {
    exit('Failed to open test1.xml.');
}
?>

test.csv的输出看起来是这样的

TITLE             COLOR
Apple iPhone 5S   black
Nokia Lumia 830   black

正如你所看到的,我只设法将每个文件加载到一个变量中,对于每个文件,我必须编写if语句,这会使脚本太大,所以我想知道是否可以将所有文件加载到数组中,用一个代码块处理它们,因为xml元素是相同的,并输出到一个.csv文件?本质上,我需要相同的test.csv输出,只需要较少的php代码。

提前谢谢。

除了使用数组之外,PHP中还有更多内容可以使它变得更加简单。就像数组可以表示文件列表一样,PHP中的其他构造也可以。

例如,由于您拥有的XML文件很可能位于特定目录中,并且的文件名遵循某种模式,因此这些文件可以很容易地用GlobIterator表示:

$inputFiles = new GlobIterator(__DIR__ . '/*.xml');

然后你可以foreach覆盖它们,我稍后将用另一个例子来展示。

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

当你遵循这样一个通用模型时,你的代码结构会更容易,而有了更好的结构,你通常会有更少的代码。即使不是,代码的每一部分都更独立、更小,这通常是您想要的。

在我用GlobIterator回答的开头显示的上述XML文件列表旁边,还有其他Iterator[/strong>可以帮助处理XML数据。

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

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

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;
        }
    }
}

正如这个示例性的Generator函数所示,它遍历所有$files,尝试将它们加载为SimpleXMLElement,如果成功,则遍历所有<phone>元素,生成

这意味着,如果函数extract_phonesforeach内被调用,则该循环将使每个<phone>元素都为SimpleXMLElement:

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实体):

title,color
"""Apple iPhone 5S""",black
"Nokia Lumia 830",black

所有这些都需要我上面已经展示过的生成器函数,它本身也有直接的代码。其他所有东西都已经使用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());

感谢@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);
?>