I'm trying to create an excel file, about 10 worksheets of 3 columns and roughly 30 rows each. I'm trying to highlight some groups of cells on each worksheet by setting some style properties (in addition to a couple cell mergers and column resizing). I'm finding that the document drops the styles after about the 4th worksheet.
My question is: can I do something to increase the number of styles I can apply to my document? Could it be that I am neglecting to do some cleanup? Some setting I'm missing?
I noticed some memory issue questions on SO that seemed related, so I checked the memory limits and tried cacheing. As far as I can tell, that doesn't seem to be the issue (please refute me if I'm wrong though!).
I created a toy example to demonstrate the problem. On my test server, the styles give out on the 3rd worksheet (about 50 applications).
Toy example (EDIT: Due to this answer I changed the example a little so that the styles are clearly in disjoint regions).
EDIT: I tried the same thing on a different server (perhaps a slightly newer version of PHPExcel as well) and all the styles appear to be preserved in Excel5 formatted output, even after increasing the complexity and size.
<?php
require_once 'classes/PHPExcel.php';
ini_set('memory_limit','64M'); // The default memory_limit in php.ini is at least this as well
$cacheMethod = \PHPExcel_CachedObjectStorageFactory::cache_to_phpTemp;
$cacheSettings=array( 'memoryCacheSize'=>'32MB');
\PHPExcel_Settings::setCacheStorageMethod($cacheMethod, $cacheSettings);
$o = new \PHPExcel();
$style1 = array(
'fill'=>array(
'type'=> \PHPExcel_Style_Fill::FILL_SOLID,
'color'=>array('rgb'=>'CCFFCC'),
),
'font'=>array(
'size'=>17,
'name'=>'Calibri Light',
'bold'=>false
),
);
$style2 = array(
'fill'=>array(
'type'=> \PHPExcel_Style_Fill::FILL_SOLID,
'color'=>array('rgb'=>'FFCCCC'),
),
'font'=>array(
'size'=>17,
'name'=>'Calibri Light',
'bold'=>false
),
);
$maxws = 10;
$maxrow=40;
for ($ws=0;$ws<$maxws;$ws++){
$o->setActiveSheetIndex($ws);
$o->getActiveSheet()->setTitle("TEST $ws");
for ($row=1;$row<$maxrow; $row++){
if ($row % 2){
$o->getActiveSheet()
->getCell("A$row")
->setValue("Styled!");
$o->getActiveSheet() ->getStyle("A$row") ->applyFromArray($style1);
}else{
$o->getActiveSheet()
->getCell("A$row")
->setValue("Default");
}
$o->getActiveSheet()
->getCell("B$row")
->setValue("Default");
if ( ! ($row % 2)){
$o->getActiveSheet()
->getCell("C$row")
->setValue("Other style!");
$o->getActiveSheet() ->getStyle("C$row") ->applyFromArray($style2);
}else{
$o->getActiveSheet()
->getCell("C$row")
->setValue("Default");
}
}
if ($ws+1<$maxws) $o->createSheet($ws+1);
}
//echo "Peak memory usage ". (memory_get_peak_usage(true)/1024/1024) . " MB\r\n"; die();
$filename = 'export_test.xls';
header("Content-Type: application/vnd.ms-excel");
header("Content-Disposition: attachment; filename=\"$filename.xls\"");
header("Cache-Control: max-age=0");
$objWriter = \PHPExcel_IOFactory::createWriter($o, "Excel5");
$objWriter->save("php://output");
exit;
Style limitations for OfficeOpenXML .xlsx files (Excel2007 Writer):
Unique cell formats/cell styles: 64,000
Fill styles: 256
Line weight and styles: 256
Unique font types: 1,024 global fonts available for use; 512 per workbook
Number formats in a workbook: Between 200 and 250, depending on the language version of Excel that you have installed
Style limitations for BIFF .xls files (Excel5 Writer):
Colours in a workbook: 56
Cell styles in a workbook: 4,000
Custom number formats: Between 200 and 250, depending on the language version of Excel you have installed.
Where possible, try to set styles in PHPExcel for a range of cells, rather than for individual cells; so rather than do
$o->getActiveSheet() ->getStyle("A$row") ->applyFromArray($style);
in your for loop, do
$o->getActiveSheet() ->getStyle("A1:A$maxrow") ->applyFromArray($style);
after the for loop has finished
This is not a complete answer but solves the issue for me.
The problem was the export format "Excel5" in the createWriter factory method. Switching to "Excel2007" fixes the issue (the styles I expect to see appear correctly). This works both for my real application and the toy example.
In code:
header("Content-Disposition: attachment; filename=\"$filename.xlsx\"");
header("Cache-Control: max-age=0");
$objWriter = \PHPExcel_IOFactory::createWriter($o, "Excel2007");
Related
I am trying to open an existing excel file, modify some cells and save it. I am using Excel2007 for reader and writer.
The input file is about 1 MB large and it has few formulas, protected data and hidden rows and columns and worksheets which I do not modify.
I am able to load the data and read and write some values into it, which I check with various var_dumps in the code.
The problem is while saving it. It throws some fatal errors on timing outs and also if it writes the file the file size is bloated to 9.2 MB, which is okay if I can open it.
code snippet - nothing fancy.
$objReader = PHPExcel_IOFactory::createReader('Excel2007');
$objPHPExcel = $objReader->load($inputFile);
$objPHPExcel->setActiveSheetIndex(2);
$activeSheet = $objPHPExcel->getActiveSheet();
$currCell = $activeSheet->getCell("O3");
$cellValidation = $currCell->getDataValidation("O3");
$values = array();
if ($cellValidation->getShowDropDown() == true)
{
$values = $cellValidation->getFormula1();
$valArray = explode(",", $values);
$currCell->setValue($valArray[0]);
}
$objWriter = PHPExcel_IOFactory::createWriter($objPHPExcel, 'Excel2007');
$objWriter -> setPreCalculateFormulas(false);
$objWriter->save($outputFile);
I use MS Excel 2010 to open the resultant file but it just takes forever and has not opened it even once.
Please help me to troubleshoot this by giving me pointers as to where I should be looking.
Any help is greatly appreciated.
Instead of saving it to a file, save it to php://outputDocs:
$objWriter->save('php://output');
This will send it AS-IS to the browser.
You want to add some headersDocs first, like it's common with file downloads, so the browser knows which type that file is and how it should be named (the filename):
// We'll be outputting an excel file
header('Content-type: application/vnd.ms-excel');
// It will be called file.xls
header('Content-Disposition: attachment; filename="file.xls"');
// Write file to the browser
$objWriter->save('php://output');
First do the headers, then the save. For the excel headers see as well the following question: Setting mime type for excel document.
So the final code would have below lines -
// Save Excel 2007 file
#echo date('H:i:s') . " Write to Excel2007 format\n";
$objWriter = PHPExcel_IOFactory::createWriter($objPHPExcel, 'Excel2007');
ob_end_clean();
// We'll be outputting an excel file
header('Content-type: application/vnd.ms-excel');
// It will be called file.xls
header('Content-Disposition: attachment; filename="file.xlsx"');
$objWriter->save('php://output');
I think this line:
ob_end_clean();
Should solve your problem.
Thanks!
There's a whole lot of reasons for that "bloat" and it very much depends on the actual data in the worksheet, but MS Excel itself uses a lot of different techniques to keep the filesize small, whereas PHPExcel writes a simple version of the OfficeOpenXML format.
For example, MS Excel looks at the string content of all cells, and stores the individual strings in a string table. If a string is used by two or more cells, there will only be a single entry in the string table. However, there's a performance overhead in checking if a string already exists in the string table, so PHPExcel doesn't perform that check but will duplicate entries in the string table. This means that it will create a large file because of the duplication, but keeps the save speed as fast as possible.
Similarly, MS Excel looks at all formulae, and if the formula is similar to an existing formula (with only a row/column offset difference) it will store it as a shared formula rather than a cell formula, so the actual formula data is only stored once. Again, PHPExcel won't perform this check, because it is a big performance overhead in the save, so it stores every formula as a cell formula rather than a shared formula.
And no, I can't explain why the file doesn't load in MS Excel 2010, nor will I be able to explain it without being able to run the whole thing through debug
So I am exporting an Excel file I created with PHPExcel to PDF. My only problem is when the content runs over the width of the page (not the length) that content disappears. Only a blank page is created where that content should be. I've tried inserting a column break with a line like this example from the documentation, but only row breaks seem to work with PDF:
$objPHPExcel->getActiveSheet()->setBreak( 'D10' , \PHPExcel_Worksheet::BREAK_COLUMN );
It's worth mentioning I'm working from within a Symfony2 controller, hence the slash in front of the PHPExcel enum.
My ideal solution is to have any extra content run over to a second page that can be placed alongside the first page to show the entire table as it appears in a real Excel document.
This is a limitation of the external library used by PHPExcel to render html output as PDF (namely tcPDF).
While I'm aware of a number of other libraries that can generate PDF from HTML (such as domPdf and mPdf) and future versions of PHPExcel will allow you to select which of these libraries you wish to use, none of them seem capable of generating multiple pages to show data from the full width of a worksheet.
Unless anybody can suggest alternatives, I'm afraid that this will remain a limitation for the foreseeable future.
I was having a similar problem vertically,
the solution i found was to use this
$highestRow = $objPHPExcel->setActiveSheetIndex(0)
->getHighestRow();
$objPHPExcel->setActiveSheetIndex(0)
->getPageSetup()
->setPrintArea("A1:I" . $highestRow )
->setFitToHeight(255);
i would suggest trying something similar with width.
for example :
$highestColumn= $objPHPExcel->setActiveSheetIndex(0)
->getHighestColumn();
$objPHPExcel->setActiveSheetIndex(0)
->getPageSetup()
->setPrintArea("A1:" . $highestColumn . "10" )
->setFitToWidth(255);
This is right now I am using.
$mimeType = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
header('Content-Description: File Transfer');
header('Content-Type: ' . $mimeType);
header('Content-Disposition: attachment; filename='.basename($type.'.xlsx'));
header('Content-Transfer-Encoding: binary');
header('Expires: 0');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Pragma: public');
print "$header\n$data";
exit;
$header variable contains the header row of excel to be generated and looks like this
$header= "Business_Name\tBusiness_Type\tType";
separated by \t
and $data contains rows to be generated under header columns. They are also separated by \t and a row is terminated by \n.
With the current setup file is downloaded but it is not opening with ms excel and showing this message.
Excel cannot open the file "file name"
because the file format or file
extension is not valid. Verify that
the file format has not been corrupted
and that the file extension matches
the format of the file.
What header should be sent to server? or how do I generate that file?
I achieve this in a fast, sort of cheapskate way - because it's long and winded I'll just explain it in concept rather than code.
XLSX adheres to ISO 29500 which is publicly available if you want to manipulate a document thoroughly in php. Otherwise, realise that xlsx files are zipped archives of a bunch of xml files.
Make a template that you want, say it has alternating rows with styles of different types, making that in excel or an open xml editor of some description. Make sure you put some data in there, and make sure some fields are equal (just for learning purposes).
Then save your file as xlsx, rename it .zip, or open it in an archive extractor and observe the contents.
Firstly, note the [Content_Types].xml file, this describes the location of the major files in the archive and the standards to which it itself adheres and the content types of those files.
Everything outside the xl/ folder is just meta data really. But observe docProps/core.xml contains author, modification and timestamp information - which you can replace in php when you recreate this file. Also everything that is pointed to say, docProps/core.xml can be renamed to your tastes, [Content_Types].xml can't.
Okay so now you understand this, you'll begin observing ids thrown around the place. They love to use this in the file format, everything refers to everything else by its index in a particular xml property list or similar. They also usually describe the quantity of items in such lists.
In xl/ you'll see themes.xml, styles.xml, workbook.xml, sharedStrings.xml, _rels/, worksheets/.
Styles is going to be inflated with a whole lot of unnecessary styles that excel builds by default if you used it. But you should be able to see how these styles work such that you can customise your own.
Themes to me is rather pointless so I delete it and its referenced ids throughout.
Next up you'll see workbook, that's the file containing information regarding the sheets which are inside of the spreadsheet document since you can have more than 1 obviously. It also contains some sheet metadata such as its size etc.
Now comes the first big hua you'll encounter. sharedStrings.xml is a weird file which stores all the information that will be inserted into cells in a static spreadsheet. They are indexed, but the engine reading the document figures out what their indexes are. Anything which repeats can be referred back to its old index in the sheet itself (inside worksheets folder) as to save on file size in large documents with repeated values.
Not the attributes count and uniquecount in the sst element and what they obviously mean.
This is the stage in php where you populate an array of data containing what you want in your sheet, and dump it into an xml formatted list such as this file appears. Also note these files don't need to be jammed up without newlines or linefeed characters as with or without is still valid xml and they will work in readers regardless.
Check out the _rels folder, it's fairly obvious again.
Lastly is the sheet itself. The numbers in fields here refer to the indexed locations of strings in sharedStrings.xml. The attribute s is the style, t is the type of data in the field. R is the cell location though why it needs that is beyond me when it could really be figured out rather easily.
Producing this file in php shouldn't be too difficult either. Just use your indexes from your data array you used to make your sharedStrings.xml file.
Oh also sheet has column width information in it which you can figure out based on the font you used and automatically size them in php too if need be.
Lastly is the packaging of it all in php.
My code is in a class which receives data and specific saved files I created with excel to keep it simple.
$this->folder_structure_simple = Array(
"_rels/.rels" => "_rels__rels",
"docProps/app.xml" => "docProps_app_xml",
"docProps/core.xml" => "docProps_core_xml",
"xl/_rels/workbook.xml.rels",
"xl/theme/theme1.xml",
"xl/worksheets/sheet1.xml",
"xl/sharedStrings.xml",
"xl/styles.xml",
"xl/workbook.xml",
"[Content_Types].xml" => "Content_Types_xml"
);
$zip = new ZipArchive;
$res = $zip->open($this->file_name, ZipArchive::CREATE);
if($res === TRUE){
foreach($this->folder_structure_simple as $file => $function){
$zip->addFromString($file, $this->$funtion);
}
$zip->close();
echo 'ok';
}else{
return FALSE;
}
And functions produce the required data. Very fast, not very flexible.
What you have is actually a CSV file. Depending on your OS, your browser and your Excel version, then the browser will differently let you or not let your open the extensions CSV, XLS XLSX with the Excel software.
If you do want to have your data opened with Excel, then you can merge the data with an Excel template using OpenTBS. Use version 1.6.0 (or greater) which is currently in Release Candidate because it brings major facilities for Excel files.
In your title there is "no excel library PHP". I don't know why you have this specification but OpenTBS is not exactly an Excel library. It's a PHP tool for merging OpenOffice and Ms Office documents using templates.
What you have a CSV, not an XLSX file. XLSX is a ZIP-wrapped blob of XML. Change your MIME type to text/csv.
I have a table within a mysql database. What i want to do, is in a php page, run a select statement and output the result into an excel file.
I've seen quite a few tutorials out there and tried them, however these output the contents of the webpage into the excel file. I just want the mysql table data and headers.
Is there a way to do this?
You can use one of the available PHP libraries for that. One, two.
One thing that is pretty fast to develop but not as neat as using a LIBRARY for that is:
Perform your MySQL query
Create an HTML table with the results
Set content type of the response to: application/vnd.ms-excel
In most browsers, using that page will open the HTML table as a spreadsheet in Excel. You can use other HTTP headers to suggest file name for that file.
I've used PHPExcel for quite a while now and I must say, it's pretty easy to use if you can read the doc.
Did what you want in an afternoon (reading the doc, trying stuff and finalizing)
Definitely you have to use a component to generate a Excel file. I use this lib:
Spreadsheet::WriteExcel: A library for generating Excel Spreadsheets
Copyright (C) 2002 Xavier Noguer xnoguer#rezebra.com
Here is a small snippet:
header("Content-type: application/vnd.ms-excel");
header("Content-Disposition: attachment; filename=$filename" );
header("Expires: 0");
header("Cache-Control: must-revalidate, post-check=0,pre-check=0");
header("Pragma: public");
$formato1 =& $workbook->add_format();
$formato1->set_border(1);
$formato1->set_bg_color('white');
$formato1->set_fg_color('grey');
$formato1->set_pattern();
$formato1->set_size(10);
$formato1->set_color('white');
$formato1->set_align('center');
$formato1->set_bold();
$formato2 =& $workbook->add_format();
$formato2->set_size(10);
$formato2->set_border(1);
$worksheet1 =& $workbook->add_worksheet('Relatório de Inscrições');
$linha = 1;
//Query dos dados das inscrições recebidas
$sql = "select
A.Name, A.Code
from
Customers A
order by
A.Name";
$resultado = $conn ->_execute($sql);
$worksheet1->write_string(0, 0, 'Name', $formato1);
$worksheet1->write_string(0, 1, 'Code', $formato1);
for($a = 0; $a < count($resultado); $a++)
{
$row = $resultado[$a];
$worksheet1->write_string($linha, 0, utf8_decode($row->Name), $formato2);
$worksheet1->write_number($linha, 1, $row->Code, $formato2);
$linha++;
}
$workbook->close();
I know this isn't the right place to ask about this specific vague problem, but maybe someone knows this library well enough to enlighten me. Here is the thing:
I am writting an Excel5 over an existing Excel file with PHPExcel. I need to upload that Excel to the Zoom website, so it can provide me with a list of tracking numbers. However, for some reason the library they are using to read the uploaded Excel files cannot read the rows written by PHPExcel and the only solution I've found so far is to manually copy the contents of my dynamically generated Excel to another document using MS Excel 2007.
In other words, the Zoom website can read the rows written natively by Excel but not rows written by PHPExcel. My file has only one single sheet, and I can open it no problem with Excel 2007.
Even if I manually add some rows to the template and then add more rows with PHPExcel, Zoom will read the rows written manually by me, but not the rows written by PHPExcel.
This is how I'm doing it:
// Starting with the PHPExcel library
$this->load->library('PHPExcel');
$this->load->library('PHPExcel/IOFactory');
$template_file = 'zoom_tracking_template.xls';
$i = 3;
$objReader = IOFactory::createReader('Excel5');
$objPHPExcel = $objReader->load($template_file);
$objPHPExcel->setActiveSheetIndex(0);
// Fetching ML payments
foreach($payments as $row)
{
$objPHPExcel->getActiveSheet()->setCellValue('A'.$i, 'VANESSA NEISZER');
$objPHPExcel->getActiveSheet()->setCellValue('B'.$i, '02127616116');
$objPHPExcel->getActiveSheet()->setCellValue('C'.$i, '1ER PISO MINITIENDAS 199 BLVD SABANA GRANDE, CRUCE C / CALLE NEGRIN');
$objPHPExcel->getActiveSheet()->setCellValue('D'.$i, $row->mailing_city);
$objPHPExcel->getActiveSheet()->setCellValue('E'.$i, $row->mailing_name);
$objPHPExcel->getActiveSheet()->setCellValue('F'.$i, $row->mailing_name);
$objPHPExcel->getActiveSheet()->setCellValue('G'.$i, $row->mailing_personal_id);
$objPHPExcel->getActiveSheet()->setCellValue('H'.$i, $row->mailing_phone);
$objPHPExcel->getActiveSheet()->setCellValue('I'.$i, $row->mailing_address1.' '.$row->mailing_address2);
$objPHPExcel->getActiveSheet()->setCellValue('J'.$i, $row->nickname);
$objPHPExcel->getActiveSheet()->setCellValue('K'.$i, '1');
$objPHPExcel->getActiveSheet()->setCellValue('L'.$i, '0.3');
$objPHPExcel->getActiveSheet()->setCellValue('M'.$i, 'M');
$objPHPExcel->getActiveSheet()->setCellValue('N'.$i, 'PRODUCTO');
$objPHPExcel->getActiveSheet()->setCellValue('O'.$i, '0');
$i++;
}
$objPHPExcel->setActiveSheetIndex(0);
$objWriter = IOFactory::createWriter($objPHPExcel, 'Excel5');
// Sending headers to force the user to download the file
header('Content-Type: application/vnd.ms-excel');
header('Content-Disposition: attachment;filename="Envios'.date('dMy').'.xls"');
header('Cache-Control: max-age=0');
$objWriter->save('php://output');
I have no clue of what PHP library they are using to read Excel files and I am certain they wont tell me if I ask them. I know they use PHP, and their library only read Excel 2003 files, however, I don't know why they can't read my files but they can read other files written manually on MS Excel.
Any clues, ideas or suggestions I could try would be greatly appreciated.
And PHPExcel's main developer is looking at this issue (among others), somewhere in between trying to find a new day job and having a life. I'm not familiar with the zoom website, or the software that they use. PHPExcel BIFF8 files can be read by Excel, OOCalc and Gnumeric without error... but a couple of questions spring to mind.
What version of PHPExcel?
Does any of the data contain UTF-8 characters?
Are there any formulae in the template worksheet?
If so, what are they?