Есть ли способ получить имена рабочих листов из файлов Excel размером более 20 МБ с помощью PHPExcel? - PullRequest
5 голосов
/ 28 декабря 2010

Я использую PHPExcel для чтения данных из файлов Excel.

С помощью следующего кода я могу прочитать один конкретный лист из 3MB Excel файл всего за пару секунд.Работает хорошо.

Однако теперь у меня есть 27MB и 88MB файлы 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;

ADDENDUM:

Я нашел какой-то код, который подходит близко, но, кажется, он не всегда точен, например, здесь он пропустил второй листв файле размером 27 МБ:

alt text

и здесь он получил только третий рабочий лист и пропустил еще 3:

alt text

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

Ответы [ 2 ]

3 голосов
/ 28 декабря 2010

К сожалению, невозможно прочитать имена рабочих листов без загрузки всего файла.

Использование номера индекса вместо имени при вызове setLoadSheetsOnly () не даст предсказуемый результат:логика кода, которая выполняет эту проверку, использует in_array () для проверки, находится ли имя листа, которое он собирается прочитать, в массиве имен листов для чтения.например,

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

Я подозреваю, что сравнение строки с числовым значением даст истинный результат для 0 == "mySheetName" при выполнении этого теста (на основе свободных правил PHP для типизации и сравнения)).

Возможно, я мог бы предоставить метод Reader, который бы возвращал список имен рабочих листов без фактической загрузки всего файла, хотя это могло бы привести к снижению производительности.

EDIT

Если вы добавите следующий метод в классы / 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, но (пока) он, кажется, делает то, что вам нужно.

EDIT2

Эквивалентный метод дляЧитатель Excel5

/**
 * 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, но все равно должен быть быстрее, чем синтаксический анализ всего файла .xls только для имен листов, потому что я анализирую только глобальный поток.

2 голосов
/ 04 апреля 2012

Я не хотел вносить изменения в 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 или более поздней версии, но сделал то, что мне нужно

...