1

XMLからCSVへの変換については多くの質問がありますが、私のものは非常に正確なので、ここで質問します。

私は在庫を脅かし、次のようなXMLファイルを取得します。

<item>
   <sku>abc</sku>
   <title>un livre</title>
   <price>42</price>
   <attributes>
      <attribute>
          <name>Nombre de pages</name>
          <value>123</value>
      </attribute>
      <attribute>
          <name>Auteur</name>
          <value>bob la mouche</value>
      </attribute>
   </attributes>
   <contributors>
      <contributor>toto</contributor>
      <contributor>titi</contributor>
      <contributor>tata</contributor>
   </contributors>
</item>

2D形式に変換する必要があります。配列/オブジェクトの配置のようなよく知られた形式を使用したいのですが、列は次のようになります。

sku
title
price
attributes.attribute[0].name
attributes.attribute[0].value
attributes.attribute[1].name
attributes.attribute[1].value
contributors.contributor[0]
contributors.contributor[1]
contributors.contributor[2]

直接の答えがない場合は、私のためにコーディングしないでください。私はそれを作成する方法を知っており、喜んであなたにそれを共有します。しかし、それは直接的な方法があるかどうかを知るためだけのものです(たとえば、xml解析ライブラリのどこかに存在する非常にエキゾチックなXMLメソッドを使用するなど)。

ありがとう

4

2 に答える 2

1

さて、何をすべきかの大まかなフレームです:

from lxml import etree
import csv

tree = etree.fromstring(xml_string, parser=etree.XMLParser())
with open(outfilepath, 'w') as f:
    writer = csv.writer(f)
    writer.writerow(<fieldnames>)
    for item_node in tree.xpath('//item'):
        var1 = item_node.xpath('.../text()')[0]
        ...
        writer.writerow(var1, var2, ...)

XML が非常に大きく、メモリに収まらない場合は、ファイルから順次読み取る別のオプションもあります。それでも、数 MB の HTML をそのように解析しました。

于 2012-10-02T10:23:05.147 に答える
0

これを行う効率的な方法は見つかりませんでした。RecursiveIteratorIteratorは良い候補でしたが、残念ながら、最終ノードへの完全なパスではなく、キーとして単一の値しか提供しませんでした。

最終的に、これとまったく同じことを行うクラスを開発しました。このクラスは、任意の XML ドキュメントを取り、「配列/オブジェクト」の性質を持つ CSV を作成します。

/**
 * This class converts a multidimentional XML file into a CSV file.
 * First XML level are distinct CSV lines
 * Last XML level are values
 * Path from the first to the last XML levels are CSV column names.
 *
 * @todo May be conflicts with XML nodes with names finishing by [n].
 *              => Case <x>test</x><x>test</x><x[0]>test</x[0]>
 *                 will generate 2 times x[0] on CSV file.
 *
 * @author ninsuo <ninsuo at gmail dot com>
 */
class Xml2Csv
{

    /**
     * An array that looks like :
     *
     * array(
     *   'simple.name' => stdClass(
     *      ->names => array('real.name[0]', 'real.name[1]', ...)
     *      ->positions => array('position-in-rows-1', 'position-in-rows-2', ...)
     *   ),
     *   ...
     * )
     *
     * Allow us to create dynamic column names according to
     * content disposition.
     *
     * @access private
     * @var array
     */
    private $columnNames;

    /**
     * Rows of CSV file
     *
     * @access private
     * @var array
     */
    private $rows;

    /**
     * Current row number
     *
     * @access private
     * @var int
     */
    private $rowNumber;

    public function convert($xmlSource, $csvTarget)
    {
        $this->_checkSourceAndTarget($xmlSource, $csvTarget);

        libxml_use_internal_errors();
        try
        {
            $tree = new SimpleXMLIterator($xmlSource, 0, true);
        }
        catch (Exception $e)
        {
            throw new Exception("Can't load XML : " . $e->getMessage());
        }
        libxml_clear_errors();

        $this->_reset();

        $this->_browseXMLTree($tree);
        unset($tree);

        $this->_writeCSV($csvTarget);

        $this->_reset();
    }

    /**
     * Checks if $source file exists and is readable.
     * Checks if $target file is writable
     *
     * @access private
     * @param string $source
     * @param string $target
     * @throws Exception
     */
    private function _checkSourceAndTarget($source, $target)
    {
        if ((!is_file($source)) || (!is_readable($source)))
        {
            throw new Exception("Source file does not exist or is not readable.");
        }
        if (((is_file($target)) && (!is_writable($target))) || (!is_writable(dirname($target))))
        {
            throw new Exception("Target file is not writable.");
        }
    }

    /**
     * Reset attributes (avoid taking huge amount of memory when converting big files)
     *
     * @access private
     */
    private function _reset()
    {
        $this->columnNames = array ();
        $this->rows = array ();
        $this->rowNumber = 0;
    }

    /**
     * First XML-level are CSV rows
     *
     * @access private
     * @param SimpleXMLIterator $tree
     */
    private function _browseXMLTree($tree)
    {
        foreach ($tree as $node)
        {
            if (count($node) > 0)
            {
                $this->rows[$this->rowNumber] = array ();
                $this->_browseXMLNode($node);
                $this->rowNumber++;
            }
        }
    }

    /**
     * Browsing next XML levels until a node has no child (CSV value)
     *
     * @access private
     * @param type $node
     * @param array $path
     */
    private function _browseXMLNode($node, array &$path = array ())
    {
        array_push($path, $node->getName());
        foreach ($node as $key => $child)
        {
            if (count($child) > 0)
            {
                $this->_browseXMLNode($child, $path);
            }
            else
            {
                $this->_addValue(implode('.', $path) . '.' . $key, strval($child));
            }
        }
        array_pop($path);
    }

    /**
     * Create a CSV column if it does not exist.
     * Add a value to the given CSV column.
     *
     * @access private
     * @param string $path
     * @param string $value
     */
    private function _addValue($column, $value)
    {
        if (array_key_exists($column, $this->columnNames))
        {
            $columnInfo = $this->columnNames[$column];
            foreach ($columnInfo->positions as $position)
            {
                if (array_key_exists($position, $this->rows[$this->rowNumber]) == false)
                {
                    $this->rows[$this->rowNumber][$position] = $value;
                    return;
                }
            }
            if (count($columnInfo->positions) == 1)
            {
                $columnInfo->names[0] .= '[0]';
            }
            $columnPosition = $this->_countCSVColumns();
            array_push($columnInfo->names, $column . '[' . count($columnInfo->positions) . ']');
            array_push($columnInfo->positions, $columnPosition);
            $this->columnNames[$column] = $columnInfo;
            $this->rows[$this->rowNumber][$columnPosition] = $value;
        }
        else
        {
            $columnPosition = $this->_countCSVColumns();
            $columnInfo = new stdClass();
            $columnInfo->names[0] = $column;
            $columnInfo->positions[0] = $columnPosition;
            $this->columnNames[$column] = $columnInfo;
            $this->rows[$this->rowNumber][$columnPosition] = $value;
        }
    }

    /**
     * Return current number of columns in the CSV file.
     * Used to get index of a new column.
     *
     * @access private
     * @return int
     */
    private function _countCSVColumns()
    {
        $count = 0;
        foreach ($this->columnNames as $columnInfo)
        {
            $count += count($columnInfo->positions);
        }
        return $count;
    }

    /**
     * Write CSV file
     *
     * @access private
     * @param string $csvTarget
     */
    private function _writeCSV($csvTarget)
    {
        $columns = $this->_getCSVColumns();
        if (($handle = fopen($csvTarget, 'w')) === false)
        {
            throw new Exception("Cannot open target file : fopen() failed.");
        }
        $this->_writeCsvRow($handle, $columns);

        $columnPositions = array_keys($columns);
        $columnNumber = count($columnPositions);
        for ($currentRow = 0; ($currentRow < $this->rowNumber); $currentRow++)
        {
            $csvRow = array ();
            for ($currentColumn = 0; ($currentColumn < $columnNumber); $currentColumn++)
            {
                $position = $columnPositions[$currentColumn];
                if (array_key_exists($position, $this->rows[$currentRow]) == false)
                {
                    $csvRow[$position] = '';
                }
                else
                {
                    $csvRow[$position] = $this->rows[$currentRow][$position];
                }
            }
            $this->_writeCsvRow($handle, $csvRow);
        }

        fclose($handle);
    }

    /**
     * Return CSV columns as a single array
     *
     * @access private
     * @return array
     */
    private function _getCSVColumns()
    {
        $columns = array ();
        foreach ($this->columnNames as $columnInfo)
        {
            foreach ($columnInfo->names as $key => $name)
            {
                $columns[$columnInfo->positions[$key]] = $name;
            }
        }
        ksort($columns);
        return $columns;
    }

    /**
     * Write a row into CSV file
     *
     * @access private
     * @param resource $handle
     * @param array $csvRow
     * @throws Exception
     */
    private function _writeCsvRow($handle, $csvRow)
    {
        if (fputcsv($handle, $csvRow, "\t", '"') === false)
        {
            fclose($handle);
            throw new Exception("Cannot write target file, fputcsv() failed.");
        }
    }

}

デモ :

1/ demo.xml ファイルを作成します。

<items>
<item>
   <sku>abc 1</sku>
   <title>a book 1</title>
   <price>42 1</price>
   <attributes>
      <attribute>
          <name>Number of pages 1</name>
          <value>123 1</value>
      </attribute>
      <attribute>
          <name>Author 1</name>
          <value>Rob dude 1</value>
      </attribute>
   </attributes>
   <contributors>
      <contributor>John 1</contributor>
      <contributor>Ryan 1</contributor>
   </contributors>
</item>
<item>
   <sku>abc 2</sku>
   <title>a book 2</title>
   <price>42 2</price>
   <attributes>
      <attribute>
          <name>Number of pages 2</name>
          <value>123 2</value>
      </attribute>
      <attribute>
          <name>Author 2</name>
          <value>Rob dude 2</value>
      </attribute>
   </attributes>
   <contributors>
      <contributor>John 2</contributor>
      <contributor>Ryan 2</contributor>
   </contributors>
</item>
</items>

2/これをあなたの仕事のどこかに入れてください

$service = new Xml2Csv();
$service->convert('demo.xml', 'demo.csv');

3/ "demo.csv" の出力を確認します。

item.sku    item.title  item.price  item.attributes.attribute.name[0]   item.attributes.attribute.value[0]  item.attributes.attribute.name[1]   item.attributes.attribute.value[1]  item.contributors.contributor[0]    item.contributors.contributor[1]
"abc 1" "a book 1"  "42 1"  "Number of pages 1" "123 1" "Author 1"  "Rob dude 1"    "John 1"    "Ryan 1"
"abc 2" "a book 2"  "42 2"  "Number of pages 2" "123 2" "Author 2"  "Rob dude 2"    "John 2"    "Ryan 2"

注:これは簡単に書かれていますが、convert()メソッドにいくつかのパラメーターを追加することで、CSVセパレーターや必要なものを変更できます。

楽しみ。

于 2012-10-03T09:50:02.410 に答える