41

Given:

$this->objPHPExcelReader = PHPExcel_IOFactory::createReaderForFile($this->config['file']);
$this->objPHPExcelReader->setLoadSheetsOnly(array($this->config['worksheet']));
$this->objPHPExcelReader->setReadDataOnly(true);
$this->objPHPExcel = $this->objPHPExcelReader->load($this->config['file']);

I can iterate through the rows like this but it is very slow, i.e. in a 3MB Excel file with a worksheet that has "EL" columns, it takes about 1 second per row:

foreach ($this->objPHPExcel->setActiveSheetIndex(0)->getRowIterator() as $row)
{
    $dataset = array();
    $cellIterator = $row->getCellIterator();
    $cellIterator->setIterateOnlyExistingCells(false);
    foreach ($cellIterator as $cell)
    {
        if (!is_null($cell))
        {
            $dataset[] = $cell->getCalculatedValue();
        }
    }
    $this->datasets[] = $dataset;
}

When I iterate like this, it it significantly faster (approx. 2000 rows in 30 seconds), but I will have to convert the letters e.g. "EL" to a number:

$highestColumm = $this->objPHPExcel->setActiveSheetIndex(0)->getHighestColumn(); // e.g. "EL"
$highestRow = $this->objPHPExcel->setActiveSheetIndex(0)->getHighestRow();

$number_of_columns = 150; // TODO: figure out how to get the number of cols as int
for ($row = 1; $row < $highestRow + 1; $row++) {
    $dataset = array();
    for ($column = 0; $column < $number_of_columns; $column++) {
        $dataset[] = $this->objPHPExcel->setActiveSheetIndex(0)->getCellByColumnAndRow($column, $row)->getValue();
    }
    $this->datasets[] = $dataset;
}

Is there a way to get the highest column as an integer (e.g. "28") instead of in Excel-styled letters (e.g. "AB")?

pnuts
  • 58,317
  • 11
  • 87
  • 139
Edward Tanguay
  • 189,012
  • 314
  • 712
  • 1,047

7 Answers7

88
$colNumber = PHPExcel_Cell::columnIndexFromString($colString);

returns 1 from a $colString of 'A', 26 from 'Z', 27 from 'AA', etc.

and the (almost) reverse

$colString = PHPExcel_Cell::stringFromColumnIndex($colNumber);

returns 'A' from a $colNumber of 0, 'Z' from 25, 'AA' from 26, etc.

EDIT

A couple of useful tricks:

There is a toArray() method for the worksheet class:

$this->datasets = $this->objPHPExcel->setActiveSheetIndex(0)->toArray();

which accepts the following parameters:

* @param  mixed    $nullValue          Value returned in the array entry if a cell doesn't exist
* @param  boolean  $calculateFormulas  Should formulas be calculated?
* @param  boolean  $formatData         Should formatting be applied to cell values?
* @param  boolean  $returnCellRef      False - Return a simple array of rows and columns indexed by number counting from zero
*                                      True - Return rows and columns indexed by their actual row and column IDs

although it does use the iterators, so would be slightly slower

OR

Take advantage of PHP's ability to increment character strings Perl Style

$highestColumm = $this->objPHPExcel->setActiveSheetIndex(0)->getHighestColumn(); // e.g. "EL" 
$highestRow = $this->objPHPExcel->setActiveSheetIndex(0)->getHighestRow();  

$highestColumm++;
for ($row = 1; $row < $highestRow + 1; $row++) {     
    $dataset = array();     
    for ($column = 'A'; $column != $highestColumm; $column++) {
        $dataset[] = $this->objPHPExcel->setActiveSheetIndex(0)->getCell($column . $row)->getValue();
    }
    $this->datasets[] = $dataset;
}

and if you're processing a large number of rows, you might actually notice the performance improvement of ++$row over $row++

Mark Baker
  • 209,507
  • 32
  • 346
  • 385
  • 4
    Thanks, Mark, I chose the second solution and it works very quickly, very nice. For anyone implementing this for/next loop with letters, notice both the `$highestColumn++` so that you get the last column as well and also notice that it is `$column != $highestColumn` and not `$column <= $highestColumn` which is deceptive since it will work with single letters but if your `$highestColumn` is e.g. `EL` then your loop will go `A-B-C-D-E` and stop there. – Edward Tanguay Jan 03 '11 at 09:02
2
/**
 * Example 0 = A, 1 = B
 */
function getNameFromNumber(int $num): string {
    if ($num < 0) throw new TypeError('$num must be at least 0');

    $numeric = $num % 26;
    $letter = chr(65 + $numeric);
    $num2 = intval($num / 26);

    if ($num2 > 0) {
        return (__FUNCTION__)($num2 - 1) . $letter;
    } else {
        return $letter;
    }
}

getNameFromNumber(0) // returns "A"
jkoop
  • 113
  • 1
  • 7
ISCI
  • 346
  • 3
  • 7
2

This is a somewhat simplified version of dqhendricks answer. I have added to copies, one function assuming you enter the full excel cell reference (ie. "AB12") and the other assuming you enter just the column reference (ie. "AB"). They both return a zero based index.

Input Full Cell Reference

function getIndex ($cell) {
    // Strip cell reference down to just letters
    $let = preg_replace('/[^A-Z]/', '', $cell);

    // Iterate through each letter, starting at the back to increment the value
    for ($num = 0, $i = 0; $let != ''; $let = substr($let, 0, -1), $i++)
        $num += (ord(substr($let, -1)) - 65) * pow(26, $i);

    return $num;
}

Input Column Reference Only

function getIndex ($let) {
    // Iterate through each letter, starting at the back to increment the value
    for ($num = 0, $i = 0; $let != ''; $let = substr($let, 0, -1), $i++)
        $num += (ord(substr($let, -1)) - 65) * pow(26, $i);

    return $num;
}

The function goes from the back of the string to the front to increase the value of the column. It uses the ord() function to get the numeric value of a character and then has the letter value subtracted to give the local column value. Finally it is multiplied by the current power of 26.

The Thirsty Ape
  • 983
  • 3
  • 16
  • 31
2

I suggest to convert excel to array, clean it from empty elements and then count the number of columns:

protected function getColumnsCheck($file, $col_number) {
        if (strstr($file, ".xls") != false && strstr($file, ".xlsx") != false) {
            $fileType = PHPExcel_IOFactory::identify($file);
            $objReader = PHPExcel_IOFactory::createReader($fileType);
            $objPHPExcel = $objReader->load($file);
            $columns_empty = $objPHPExcel->getActiveSheet(0)->toArray()[0]; 

            $columns = array_filter($columns_empty);

            return ($col_number==count($columns));
        }
        return false;
    }
2

Since this question is 10 years old, and the packages referenced here are not the newest ones any more:

Here is how you do it using phpspreadsheet:

$colNumber = \PhpOffice\PhpSpreadsheet\Cell\Coordinate::columnIndexFromString($colString); // e.g. 5

Source: https://phpspreadsheet.readthedocs.io/en/latest/topics/accessing-cells/

atra
  • 64
  • 4
1

Not sure if your class has a built in method, but you could always use the ord() function on each letter of the column index string. You will of course have to subtract out the base value of 'A', and multiply by 26^x for each position from the far right of the string. Something like:

    $input_string = 'BC';
    $base_value = 64;
    $decimal_value = 26;
    $column_index = 0;
    for ($i = 0; $i < strlen($input_string); $i++) {
        $char_value = ord($input_string[$i]);
        $char_value -= $base_value;
        $char_value *= pow($decimal_value, (strlen($input_string) - ($i + 1)));
        $column_index += $char_value;
    }
    echo $column_index;

Basically this would make 'BC' equal (2 * 26^1) + (3 * 26^0) = 55.

$input_string being the column index string, $base_value being the ord() value of 'A' minus 1, and $decimal_value being the value of A0. Should work up to any number column. Have tested. Hope this helps.

dqhendricks
  • 19,030
  • 11
  • 50
  • 83
  • Actually, you should test this. Doesn't give the right output. – sberry Dec 30 '10 at 16:52
  • Look at your example at the end for 'BC'. `(2 * 26) + (3 * 0) != 55`. Actually, you made the same mistake I made initially, which is why I added the if/else. – sberry Dec 30 '10 at 16:58
  • i have fixed for sure this time. my big mistake was not using powers to calc the proper decimal value. this should now give the correct value for any number column. – dqhendricks Dec 30 '10 at 17:23
  • @dqhendricks Thanks for the answer, I used it to make a condensed function in reverse. – The Thirsty Ape Jan 15 '14 at 22:51
1
/**
 * 
 */
function number_to_alphabet($number) {
    $number = intval($number);
    if ($number <= 0) {
        return '';
    }
    $alphabet = '';
    while($number != 0) {
        $p = ($number - 1) % 26;
        $number = intval(($number - $p) / 26);
        $alphabet = chr(65 + $p) . $alphabet;
    }
    return $alphabet;
}

/**
 * Required PHP 5.6.
 * @see: http://php.net/manual/en/language.operators.arithmetic.php
 */
function alphabet_to_number($string) {
    $string = strtoupper($string);
    $length = strlen($string);
    $number = 0;
    $level = 1;
    while ($length >= $level ) {
        $char = $string[$length - $level];
        $c = ord($char) - 64;        
        $number += $c * (26 ** ($level-1));
        $level++;
    }
    return $number;
}

Test:

for ($x=1; $x<=1000; $x++) {
    echo 'number_to_alphabet('.$x.') = ',$y = number_to_alphabet($x),'; ';
    echo 'alphabet_to_number('.$y.') = '.alphabet_to_number($y).'; ';
    echo PHP_EOL;
}
IjorTengab
  • 41
  • 2
  • You don't need to delete it. Just make it very clear that it's your site. For example, you could add a link under the code that says something like, "Source: My blog, [link to that post]". – Nic Oct 20 '17 at 16:22