将整数转换为Excel列(字母或字母组合)-完整的PHP示例、单元测试和解释

将整数转换为Excel列(字母或字母组合)-完整的PHP示例、单元测试和解释,php,excel,auto-generate,letter,pdostatement,Php,Excel,Auto Generate,Letter,Pdostatement,假设我们有这样一个函数,它接受PDO语句(任何查询)并自动生成excel文件(使用PHPExcel库): 然后打开与excel文件的连接: $excel = new PHPExcel(); $writer = PHPExcel_IOFactory::createWriter($excel, "Excel2007"); $sheet = $excel->getActiveSheet(); $sheet->setTitle($name); 此函数用于确保列名数组的长度与行数组中的

假设我们有这样一个函数,它接受PDO语句(任何查询)并自动生成excel文件(使用PHPExcel库):

然后打开与excel文件的连接:

$excel = new PHPExcel();

$writer = PHPExcel_IOFactory::createWriter($excel, "Excel2007");

$sheet = $excel->getActiveSheet();

$sheet->setTitle($name);
此函数用于确保列名数组的长度与行数组中的字段数相同

/**
* Take the array containing the $columnName for data export (CSV, Excel) and make sure
* that it is the number of entry as there are fields in $row.
*
* If column name are missing, we will use the column name used in the query.
*
* Return the merged array
*
* @param $columnName Array containing the column names
* @param $row Array produce by fetch(PDO::FETCH_ASSOC).
*
* @return Array ($columnName)
*/
private static function validateColumnNameArray(array &$columnName, array &$row){

    $buffer = array();

    $colPDO = count($row);

    $count = count($columnName);

    if ($count < $colPDO){

        foreach ($row as $key => $value){

            $buffer[] = $key;   
        }

        for($index = $count; $index < $colPDO; $index++){

            $columnName[] = $buffer[$index];
        }
    }

    unset($buffer);

    return $columnName;
}
如果它是我们添加到excel文件的第一行,那么我们设置基本格式:

if ($rows === 0){

            $columnName = self::validateColumnNameArray($columnName, $row);

            $totalCols = count($row);

            $sheet->getStyle('A1:' . self::convertNumberToExcelCol($totalCols) . '1')->getFont()->setBold(true)->setSize(12);

            for ($column = 1; $column <= $totalCols; $column++){

                $sheet->getCell(self::convertNumberToExcelCol($column) . '1')->setValue($columnName[$column - 1]);

                $sheet->getColumnDimension(self::convertNumberToExcelCol($column))->setAutoSize(true);
            }

            $rows = 1;
        }
我个人只在午夜对源文件和数据库进行自动备份时才使用这种方法。白天运行它会增加删除其他用户使用的文件的机会

这就是为什么我正在考虑最佳做法,即在通过浏览器发送给用户时立即删除文件,并将清理方法仅用于维护目的

如果没有错误,我将行数减少1,因为我不想计算标题行。如果将标题行看作数据行,则可以删除该行。

最后,这些方法返回访问新创建文件的路径:

return (empty($errorMessage) ? $path : "");
但前提是没有错误。因此,如果函数返回空字符串,则表示发生了错误

由于PHP的类型松散,您可以返回任何内容,包括布尔值,甚至错误消息,但出于一致性目的,我更喜欢始终返回相同的数据类型。我个人最喜欢的方法是布尔返回值和通过引用传递的错误消息变量。所以我可以使用这样的代码:

$errorMessage = "";

            if ($_SESSION["adminAccount"]->updateAccountInfo($errorMessage,
                                                        (isset($_POST['FIRST_NAME_TEXT']) ? $_POST['FIRST_NAME_TEXT'] : $_SESSION["adminAccount"]->getFirstName()),
                                                        (isset($_POST['LAST_NAME_TEXT']) ? $_POST['LAST_NAME_TEXT'] : $_SESSION["adminAccount"]->getLastName()),
                                                        (isset($_POST['EMAIL_TEXT']) ? $_POST['EMAIL_TEXT'] : $_SESSION["adminAccount"]->getEmail()))){

                PageManager::displaySuccessMessage("Your account information were saved with success.", "USER_ACCOUNT_INFORMATION_SAVED");

            }
            else{

                PageManager::displayErrorMessage($errorMessage);    
            }
这样,错误由类方法在内部管理,并且可以根据视图上下文调整成功消息。布尔返回值用于确定是否必须显示错误或成功消息

注意:单元测试将包含在我的答案中


来自蒙特利尔的Jonathan Parent-Lévesque现在有一种方法可以将整数转换为excel列

excel列可以是一到三个字母的组合,最多可达16383(XFD),这是excel文件中列的当前限制:

$excel = new PHPExcel();

$writer = PHPExcel_IOFactory::createWriter($excel, "Excel2007");

$sheet = $excel->getActiveSheet();

$sheet->setTitle($name);

我在函数旁边使用一个包含所有英文字母的数组:

public static $letters = array(1 => "A", 2 => "B", 3=> "C", 4 => "D", 5 => "E", 6 => "F", 7 => "G", 8=> "H", 9=> "I", 10 => "J", 11 =>"K", 12 => "L", 13 => "M", 14 => "N", 15=> "O", 16 => "P", 17 => "Q", 18 => "R", 19 => "S", 20 => "T", 21 => "U", 22 => "V", 23 => "W", 24 => "X", 25 => "Y", 26 => "Z");
该函数接受单个参数,并断言该值为数值,并且在以下限制之间:

public static function convertNumberToExcelCol($number){

    $column = "";

    if (is_numeric($number) and $number > 0 and $number < 16385){
让我们测试一下:

for ($index = 1; $index < 27; $index++){

        $this->assertEquals(FileManager::$letters[$index], FileManager::convertNumberToExcelCol($index));   
    }
由于英文字母表包含26个字母,因此整个算法也基于26个字母

对于大多数值,我们只需将[number]/26四舍五入即可得到第一个字母,使用数字[number]%26的整个除法(除法的剩余部分)即可得到第二个字母

$first = floor($number / 26);

$second = $number % 26;
以下是一些示例:

  • 27=AA
  • 28=AB
  • 51=AY
  • 53=BA
如果[number]%26=0,举例来说:52、78、104等等,那么我们必须使用一些不同的编码

例如,52=AZ,52/26=2(第一个字母)和52%26=0(第二个字母)

在我们的字母数组中,2=B和0超出了界限。这就是为什么我们必须将第一个字母的值减少1,将第二个字母的值强制为26

$first = floor($number / 26) - 1;

$second = 26;
测试时间:

for ($first = 1; $first < 27; $first++){

        $temp = $first * 26;

        for ($second = 1; $second < 27; $second++){
            $this->assertEquals(FileManager::$letters[$first] . FileManager::$letters[$second], FileManager::convertNumberToExcelCol($temp + $second)); 
        }
    }
($first=1;$first<27;$first++)的
{
$temp=$first*26;
对于($second=1;$second<27;$second++){
$this->assertEquals(文件管理器::$letters[$first]。文件管理器::$letters[$second],文件管理器::convertNumberToExcelCol($temp+$second));
}
}
当我们试图管理三封信时,真正的挑战来了。当然,您可能不会有一个超过702个字段的查询,但是为了该方法的完整性和一些真正的编程挑战,让我们检查一下如何做到这一点

起初,我不得不尝试进行错误测试,最后进行了以下编码:

elseif($number < 1379){

    $column = self::$letters[floor($number / 702)] . self::convertNumberToExcelCol($number % 702 + 26);
}
elseif($number < 2028){

    $column = self::$letters[floor($number / 676)] . self::convertNumberToExcelCol($number % 676);
}
elseif ($number < 2055){

    $column = self::$letters[floor($number / 702)] . self::convertNumberToExcelCol($number % 702 + 52);
}
elseif($number < 2704){

    $column = self::$letters[floor($number / 676)] . self::convertNumberToExcelCol($number % 676);  
}
elseif ($number < 2731) {

    $column = self::$letters[floor($number / 702)] . self::convertNumberToExcelCol($number % 702 + 78); 
}
elseif ($number < 3380) {

    $column = self::$letters[floor($number / 676)] . self::convertNumberToExcelCol($number % 676);      
}
elseif ($number < 3407){

    $column = self::$letters[floor($number / 702)] . self::convertNumberToExcelCol($number % 702 + 104);    
}
elseif($number<1379){
$column=self::$letters[楼层($number/702)]。self:$convertNumberToExcelCol($number%702+26);
}
elseif(数量<2028){
$column=self::$letters[floor($number/676)]。self::convertNumberToExcelCol($number%676);
}
elseif(数量<2055美元){
$column=self::$letters[楼层($number/702)]。self:$convertNumberToExcelCol($number%702+52);
}
elseif(数量<2704美元){
$column=self::$letters[floor($number/676)]。self::convertNumberToExcelCol($number%676);
}
elseif(数量<2731){
$column=self::$letters[楼层($number/702)]。self:$convertNumberToExcelCol($number%702+78);
}
elseif(数量<3380美元){
$column=self::$letters[floor($number/676)]。self::convertNumberToExcelCol($number%676);
}
elseif($3407){
$column=self::$letters[楼层($number/702)]。self:$convertNumberToExcelCol($number%702+104);
}
是的,这并不严重,你可以像这样一直走到16k

如果你仔细观察,你会发现有一个模式从中画出来。有一个数字可以除以676得到第一个字母,模676得到第二个和第三个字母。例如,2027=BBY

第二种模式是数字,第一个字母可以除以702+a补偿(26、52、78、104,…)的模。包括像703=AAA这样的数字

当然,676和702都是26的倍数

我花了很多时间计算,但我开始意识到第二种模式总是27个数字的范围,这些数字总是产生一个低于27(0-26)的数字作为这个数字的模

elseif($number < 2028){

    $column = self::$letters[floor($number / 676)] . self::convertNumberToExcelCol($number % 676);
}
elseif ($number < 2055){
elseif($number<2028){
$column=self::$letters[floor($number/676)]。self::convertNumberToExcelCol($number%676);
}
elseif(数量<2055美元){
举例来说:

  • 2028%676=0
  • 2029%676=1
  • 2054%676=26
  • 2055%676=27(现在超出范围)
现在,使用702算法的数字范围已经找到,我们必须确定如何计算
    if ($number % 26 === 0){

        $first = floor($number / 26) - 1;

        $second = 26;
    }
    else{

        $first = floor($number / 26);

        $second = $number % 26;
    }

    $column = self::$letters[$first] . self::$letters[$second];
$first = floor($number / 26);

$second = $number % 26;
$first = floor($number / 26) - 1;

$second = 26;
for ($first = 1; $first < 27; $first++){

        $temp = $first * 26;

        for ($second = 1; $second < 27; $second++){
            $this->assertEquals(FileManager::$letters[$first] . FileManager::$letters[$second], FileManager::convertNumberToExcelCol($temp + $second)); 
        }
    }
elseif($number < 1379){

    $column = self::$letters[floor($number / 702)] . self::convertNumberToExcelCol($number % 702 + 26);
}
elseif($number < 2028){

    $column = self::$letters[floor($number / 676)] . self::convertNumberToExcelCol($number % 676);
}
elseif ($number < 2055){

    $column = self::$letters[floor($number / 702)] . self::convertNumberToExcelCol($number % 702 + 52);
}
elseif($number < 2704){

    $column = self::$letters[floor($number / 676)] . self::convertNumberToExcelCol($number % 676);  
}
elseif ($number < 2731) {

    $column = self::$letters[floor($number / 702)] . self::convertNumberToExcelCol($number % 702 + 78); 
}
elseif ($number < 3380) {

    $column = self::$letters[floor($number / 676)] . self::convertNumberToExcelCol($number % 676);      
}
elseif ($number < 3407){

    $column = self::$letters[floor($number / 702)] . self::convertNumberToExcelCol($number % 702 + 104);    
}
elseif($number < 2028){

    $column = self::$letters[floor($number / 676)] . self::convertNumberToExcelCol($number % 676);
}
elseif ($number < 2055){
                if($number % 676 < 27){

                    $compensation = floor($number / 26) - 26;

                    $column = self::$letters[floor($number / 702)] . self::convertNumberToExcelCol($number % 702 + ($compensation % 26 === 0 ? $compensation : $compensation - 1));
                }
                else{
                    $column = self::$letters[floor($number / 676)] . self::convertNumberToExcelCol($number % 676);
                }
        for ($first = 1; $first < 27; $first++){

        for ($second = 1; $second < 27; $second++){

            for ($third = 1; $third < 27; $third++){

                $temp = $first *  676 + (($second * 26) + $third);

                if ($temp < 16385){

                    $this->assertEquals(FileManager::$letters[$first] . FileManager::$letters[$second] . FileManager::$letters[$third], FileManager::convertNumberToExcelCol($temp));

                }
                else{

                    $this->assertEmpty(FileManager::convertNumberToExcelCol($temp + $index));   
                }
            }
        }
    }
    /**
* Convert a $number to the letter (or combination of letters) representing a column in excel. 
*   Will return an empty string if $number is not a valid value.
*
* @param number Int must be is_numeric() and > 0 and < 16,385.
*
* @return String
*/
public static function convertNumberToExcelCol($number){

    $column = "";

    if (is_numeric($number) and $number > 0 and $number < 16385){

        if ($number < 27){

            $column = self::$letters[$number];
        }
        elseif ($number < 703){

            if ($number % 26 === 0){

                $first = floor($number / 26) - 1;

                $second = 26;
            }
            else{

                $first = floor($number / 26);

                $second = $number % 26;
            }

            $column = self::$letters[$first] . self::$letters[$second];
        }
        else{

            if($number % 676 < 27){

                $compensation = floor($number / 26) - 26;

                $column = self::$letters[floor($number / 702)] . self::convertNumberToExcelCol($number % 702 + ($compensation % 26 === 0 ? $compensation : $compensation - 1));
            }
            else{
                $column = self::$letters[floor($number / 676)] . self::convertNumberToExcelCol($number % 676);
            }   
        }
    }


    return $column;
}
    public function testvalidateFileExtention(){

    //invalid extension
    $this->assertEquals("woot", FileManager::validateFileExtention("woot", true));

    $this->assertEquals("woot", FileManager::validateFileExtention("woot", ""));

    $this->assertEquals("woot", FileManager::validateFileExtention("woot", "."));

    $this->assertEquals("woot.blu", FileManager::validateFileExtention("woot", ".b.l.u.."));


    //invalid name
    $this->assertEquals("my_file.blu", FileManager::validateFileExtention(true, ".blu"));

    $this->assertEquals("my_file.blu", FileManager::validateFileExtention("", ".blu"));

    $this->assertEquals("my_file.blu", FileManager::validateFileExtention(".woot", ".blu"));

    $this->assertEquals("woot.blu", FileManager::validateFileExtention("w.o.o.t.", ".blu"));


    //valid file name and extension
    $this->assertEquals("woot.blu", FileManager::validateFileExtention("woot", "blu"));

    $this->assertEquals("woot.blu", FileManager::validateFileExtention("woot", ".blu"));

}

public function testCreateCSVFromRS(){

    FileManager::emptyArchiveFolder();

    $errorMessage = "";
    $path = realpath(dirname(__FILE__)) . '/../Archive/woot.csv';
    $rows = 0;


    //no data to export
    $rs = $this->fetchData("SELECT id, field_id, field_value, language FROM error_message_table WHERE field_id='woot for loots'");

    $this->assertInstanceOf('PDOStatement', $rs);


    $this->assertEmpty(FileManager::createCSVFromRS($errorMessage, $rs, $rows, "woot"));

    $this->assertNotEmpty($errorMessage);

    $this->assertEquals(0, $rows);

    $this->assertFileNotExists($path);


    //data, but missing columns in the header array
    $rs = $this->fetchData("SELECT id, field_id, field_value, language FROM error_message_table LIMIT 100");

    $this->assertNotEmpty(FileManager::createCSVFromRS($errorMessage, $rs, $rows, "woot", array("homer", "simpson")));

    $this->assertInstanceOf('PDOStatement', $rs);

    $this->assertEmpty($errorMessage);

    $this->assertEquals(100, $rows);

    $this->assertFileExists($path);


    $handle = fopen($path, "r");

    $this->assertNotEquals(false, $handle);

    $row = fgetcsv($handle);


    $this->assertContains("homer", $row);

    $this->assertNotContains("id", $row);


    $this->assertContains("simpson", $row);

    $this->assertNotContains("field_id", $row);


    $this->assertContains("field_value", $row);

    $this->assertContains("language", $row);


    fclose($handle);


    //data, changing all columns in the header array
    $rs = $this->fetchData("SELECT id, field_id, field_value, language FROM error_message_table LIMIT 10");

    $this->assertNotEmpty(FileManager::createCSVFromRS($errorMessage, $rs, $rows, "woot", array("kyle", "eric", "kenny", "stan")));

    $this->assertInstanceOf('PDOStatement', $rs);

    $this->assertEmpty($errorMessage);

    $this->assertEquals(10, $rows);

    $this->assertFileExists($path);


    $handle = fopen($path, "r");

    $this->assertNotEquals(false, $handle);

    $row = fgetcsv($handle);


    $this->assertContains("kyle", $row);

    $this->assertNotContains("id", $row);


    $this->assertContains("eric", $row);

    $this->assertNotContains("field_id", $row);


    $this->assertContains("kenny", $row);

    $this->assertNotContains("field_value", $row);


    $this->assertContains("stan", $row);

    $this->assertNotContains("language", $row);


    fclose($handle);

    unlink($path);
}

public function testConvertNumberToExcelCol(){

    //invalid paramter
    $this->assertEmpty(FileManager::convertNumberToExcelCol("a"));

    $this->assertEmpty(FileManager::convertNumberToExcelCol(array()));

    $this->assertEmpty(FileManager::convertNumberToExcelCol(-1));

    $this->assertEmpty(FileManager::convertNumberToExcelCol(1000000000));


    //single letter
    for ($index = 1; $index < 27; $index++){

        $this->assertEquals(FileManager::$letters[$index], FileManager::convertNumberToExcelCol($index));   
    }

    //double letters
    for ($first = 1; $first < 27; $first++){

        $temp = $first * 26;

        for ($second = 1; $second < 27; $second++){
            $this->assertEquals(FileManager::$letters[$first] . FileManager::$letters[$second], FileManager::convertNumberToExcelCol($temp + $second)); 
        }
    }

    //tripple letters
    for ($first = 1; $first < 27; $first++){

        for ($second = 1; $second < 27; $second++){

            for ($third = 1; $third < 27; $third++){

                $temp = $first *  676 + (($second * 26) + $third);

                if ($temp < 16385){

                    $this->assertEquals(FileManager::$letters[$first] . FileManager::$letters[$second] . FileManager::$letters[$third], FileManager::convertNumberToExcelCol($temp));

                }
                else{

                    $this->assertEmpty(FileManager::convertNumberToExcelCol($temp + $index));   
                }
            }
        }
    }
}


public function testCreateExcelFromRS(){

    $errorMessage = "";
    $path = realpath(dirname(__FILE__)) . '/../Archive/woot.xlsx';
    $rows = 0;


    //no data to export
    $rs = $this->fetchData("SELECT id, field_id, field_value, language FROM error_message_table WHERE field_id='woot for loots'");

    $this->assertInstanceOf('PDOStatement', $rs);


    $this->assertEmpty(FileManager::createExcelFromRS($errorMessage, $rs, $rows, "woot"));

    $this->assertNotEmpty($errorMessage);

    $this->assertEquals(0, $rows);

    $this->assertFileNotExists($path);


    //data, but missing columns in the header array
    $rs = $this->fetchData("SELECT id, field_id, field_value, language FROM error_message_table LIMIT 100");

    $this->assertNotEmpty(FileManager::createExcelFromRS($errorMessage, $rs, $rows, "woot", array("homer", "simpson")));

    $this->assertInstanceOf('PDOStatement', $rs);

    $this->assertEmpty($errorMessage);

    $this->assertEquals(100, $rows);

    $this->assertFileExists($path);


    $reader = PHPExcel_IOFactory::createReaderForFile($path);
    $reader->setReadDataOnly(true);
    $excel = $reader->load($path);


    $this->assertEquals("homer", $excel->getSheet(0)->getCell('A1')->getValue());

    $this->assertEquals("simpson", $excel->getSheet(0)->getCell('B1')->getValue());

    $this->assertEquals("field_value", $excel->getSheet(0)->getCell('C1')->getValue());

    $this->assertContains("language", $excel->getSheet(0)->getCell('D1')->getValue());

    $excel->disconnectWorksheets();

    unset($excel);



    //data, changing all columns in the header array
    $rs = $this->fetchData("SELECT id, field_id, field_value, language FROM error_message_table LIMIT 10");

    $this->assertNotEmpty(FileManager::createExcelFromRS($errorMessage, $rs, $rows, "woot", array("kyle", "eric", "kenny", "stan")));

    $this->assertInstanceOf('PDOStatement', $rs);

    $this->assertEmpty($errorMessage);

    $this->assertEquals(10, $rows);

    $this->assertFileExists($path);


    $reader = PHPExcel_IOFactory::createReaderForFile($path);
    $reader->setReadDataOnly(true);
    $excel = $reader->load($path);


    $this->assertEquals("kyle", $excel->getSheet(0)->getCell('A1')->getValue());

    $this->assertEquals("eric", $excel->getSheet(0)->getCell('B1')->getValue());

    $this->assertEquals("kenny", $excel->getSheet(0)->getCell('C1')->getValue());

    $this->assertContains("stan", $excel->getSheet(0)->getCell('D1')->getValue());

    $excel->disconnectWorksheets();


    unlink($path);
}

private function fetchData($query, $db = "language_manager"){

    $_SESSION['domain'] = $db;

    $errorMessage = "";

    $dbManager = GeneralDBManager::getInstance();

    $rs = $dbManager->fetchData($query . ";/*th1s 1s a v4l1d qu3ry*/", $errorMessage);

    unset($dbManager);

    return $rs; 
}