5

我正在使用PHPExcel从 Excel 文件中读取数据。

使用以下代码,我可以在几秒钟内从 3MB Excel 文件中读取一个特定的工作表。效果很好。

但是,我现在有27MB88MB的 Excel 文件,我需要从中获取数据。它们太大了,甚至OpenOffice也无法打开它们。

我发现在加载工作表时可以使用索引号而不是名称,但这似乎不一致,例如,在一个特定的 Excel 文件中setLoadSheetsOnly(0)给了我第三张工作表,而setLoadSheetsOnly(1)给了我一个错误,即使文件中有四个工作表. 因此,出于某种原因,这似乎不可靠。

有没有办法可以从大文件中读出工作表的名称,以便一次只能访问其中一个工作表?

        $objReader = PHPExcel_IOFactory::createReaderForFile("data/" . $file_name);
        $objReader->setLoadSheetsOnly(array($sheet_name));
        $objReader->setReadDataOnly(true);
        $objPHPExcel = $objReader->load("data/" . $file_name);

        echo '<table border="1">';
        for ($row = 1; $row < $number_of_rows; $row++) {
            echo '<tr>';
            for ($column = 0; $column < $number_of_columns; $column++) {
                $value = $objPHPExcel->setActiveSheetIndex(0)->getCellByColumnAndRow($column, $row)->getValue();
                echo '<td>';
                echo $value . '&nbsp;';
                echo '</td>';
            }
            echo '</tr>';
        }
        echo '</table>';
        die;

附录:

我发现了一些接近的代码,但它似乎并不总是准确的,例如,它错过了 27MB 文件中的第二个工作表:

替代文字

在这里它只得到了第三个工作表,错过了其他 3 个:

替代文字

$objReader = PHPExcel_IOFactory::createReaderForFile("data/" . $file_name);
$objReader->setLoadSheetsOnly(0);
$objReader->setReadDataOnly(true);
$objPHPExcel = $objReader->load("data/" . $file_name);

echo $objPHPExcel->getSheetCount(), ' worksheets<hr/>';
$loadedSheetNames = $objPHPExcel->getSheetNames();
foreach ($loadedSheetNames as $sheetIndex => $loadedSheetName) {
    echo $sheetIndex, ' -> ', $loadedSheetName, '<br />';
}
die;
4

2 回答 2

3

不幸的是,如果不加载整个文件,就无法读取工作表的名称。

在调用 setLoadSheetsOnly() 时使用索引号而不是名称不会给出可预测的结果:执行该检查的代码逻辑使用 in_array() 来测试它要读取的工作表名是否在工作表名数组中读。例如

// check if sheet should be skipped
if (isset($this->_loadSheetsOnly) && !in_array($sheet['name'], $this->_loadSheetsOnly)) {
    continue;
}

我怀疑在执行此测试时(基于 PHP 的松散类型和比较转换规则),字符串与数值的比较将给出 0 == "mySheetName" 的真实结果。

我可能会提供一个 Reader 方法,该方法将返回工作表名称列表,而无需实际加载整个文件,尽管会影响性能。

编辑

如果将以下方法添加到 Classes/PHPExcel/Reader/Excel2007.php

/**
 * Reads names of the worksheets from a file, without loading the whole file to a PHPExcel object
 *
 * @param   string      $pFilename
 * @throws  Exception
 */
public function listWorksheetNames($pFilename)
{
    // Check if file exists
    if (!file_exists($pFilename)) {
        throw new Exception("Could not open " . $pFilename . " for reading! File does not exist.");
    }

    $worksheetNames = array();

    $zip = new ZipArchive;
    $zip->open($pFilename);

    $rels = simplexml_load_string($this->_getFromZipArchive($zip, "_rels/.rels")); //~ http://schemas.openxmlformats.org/package/2006/relationships");
    foreach ($rels->Relationship as $rel) {
        switch ($rel["Type"]) {
            case "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument":
                $xmlWorkbook = simplexml_load_string($this->_getFromZipArchive($zip, "{$rel['Target']}"));  //~ http://schemas.openxmlformats.org/spreadsheetml/2006/main");

                if ($xmlWorkbook->sheets) {
                    foreach ($xmlWorkbook->sheets->sheet as $eleSheet) {
                        // Check if sheet should be skipped
                        $worksheetNames[] = (string) $eleSheet["name"];
                    }
                }
        }
    }

    $zip->close();

    return $worksheetNames;
}

您可以使用以下方法调用它:

$inputFileType = 'Excel2007';
$inputFileName = 'biostat-behfisk-2005.xlsx';
$objReader = PHPExcel_IOFactory::createReader($inputFileType);
$worksheetNames = $objReader->listWorksheetNames($inputFileName);

foreach ($worksheetNames as $sheetName) {
    echo $sheetName, '<br />';
}

返回的 $worksheetNames 应该包含一个所有工作表名称的数组作为 UTF-8 字符串。因为它只是从 .xlsx 中读取绝对最小值来检索这些名称,所以它应该相当快。在将其检入 PHPExcel SVN 之前,我会进行更多测试,但(目前)它似乎可以满足您的需求。

编辑2

Excel5 Reader 的等效方法

/**
 * Reads names of the worksheets from a file, without loading the whole file to a PHPExcel object
 *
 * @param   string      $pFilename
 * @throws  Exception
 */
public function listWorksheetNames($pFilename)
{
    // Check if file exists
    if (!file_exists($pFilename)) {
        throw new Exception("Could not open " . $pFilename . " for reading! File does not exist.");
    }

    $worksheetNames = array();

    // Read the OLE file
    $this->_loadOLE($pFilename);

    // total byte size of Excel data (workbook global substream + sheet substreams)
    $this->_dataSize = strlen($this->_data);

    $this->_pos     = 0;
    $this->_sheets  = array();

    // Parse Workbook Global Substream
    while ($this->_pos < $this->_dataSize) {
        $code = self::_GetInt2d($this->_data, $this->_pos);

        switch ($code) {
            case self::XLS_Type_BOF:    $this->_readBof();      break;
            case self::XLS_Type_SHEET:  $this->_readSheet();    break;
            case self::XLS_Type_EOF:    $this->_readDefault();  break 2;
            default:                    $this->_readDefault();  break;
        }
    }

    foreach ($this->_sheets as $sheet) {
        if ($sheet['sheetType'] != 0x00) {
            // 0x00: Worksheet, 0x02: Chart, 0x06: Visual Basic module
            continue;
        }

        $worksheetNames[] = $sheet['name'];
    }

    return $worksheetNames;
}

效率不如 Excel2007 Reader 版本,但仍比仅针对工作表名称解析整个 .xls 文件要快,因为我只解析全局流。

于 2010-12-28T13:03:29.560 回答
2

我不想修改 phpexcel,所以我选择了这个:

public function getWorksheetNames($pFilename) {

    $worksheetNames = array ();

    $zip = zip_open ( $pFilename );
    while ( $entry = zip_read ( $zip ) ) {

        $entry_name = zip_entry_name ( $entry );
        if ($entry_name == 'xl/workbook.xml') {
            if (zip_entry_open ( $zip, $entry, "r" )) {
                $buf = zip_entry_read ( $entry, zip_entry_filesize ( $entry ) );
                $workbook = simplexml_load_string ( $buf );
                foreach ( $workbook->sheets as $sheets ) {
                    foreach( $sheets as $sheet) {
                        $attributes=$sheet->attributes();
                        $worksheetNames[]=$attributes['name'];
                    }
                }
                zip_entry_close ( $entry );
            }
            break;
        }

    }
    zip_close ( $zip );
    return $worksheetNames;
}

它仅适用于 excel 2007 或更高版本,但可以满足我的需要

于 2012-04-04T13:58:35.437 回答