I am using the latest master of PHPExcel (most recent commit March 28th 2015) to parse an xlsx file for a client, the following code reads the file without complaining but finds no data (the only output I currently get is NULL):
<?php
include 'PHPExcel/IOFactory.php';
$inputFileName = 'foo.xlsx';
try {
$objReader = PHPExcel_IOFactory::createReaderForFile($inputFileName);
$objPHPExcel = $objReader->load($inputFileName);
} catch (Exception $e) {
print_r('Error loading file "' . pathinfo($inputFileName,
PATHINFO_BASENAME) . '": ' . $e->getMessage());
}
$sheet = $objPHPExcel->getSheetByName("Orders");
var_dump($sheet);
?>
The workbook definition inside the xlsx file looks like this:
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<x:workbook xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:x="http://schemas.openxmlformats.org/spreadsheetml/2006/main">
<x:fileVersion appName="xl" lastEdited="4" lowestEdited="4" rupBuild="4506" />
<x:workbookPr defaultThemeVersion="124226" />
<x:bookViews>
<x:workbookView xWindow="480" yWindow="345" windowWidth="24495" windowHeight="11955" />
</x:bookViews>
<x:sheets>
<x:sheet name="Orders" sheetId="4" r:id="relId4" />
</x:sheets>
<x:calcPr calcId="125725" />
</x:workbook>
I think it's not working out that there's an "Orders" sheet because it's looking for "sheets" in the default namespace rather than the "x" namespace. Sorry if terminology is a bit gibberish there but hopefully you can see what I mean.
There is definitely data there, in that worksheet, because I can open the file in LibreOffice and see it.
Is there a way to get PHPExcel to use the x namespace as the default or in some other way get it to load worksheets defined in this way?
In my quest to find out I found a bug report from 20101 regarding a similar issue which doesn't seem to have been addressed.
This issue is tracked at https://github.com/PHPOffice/PHPExcel/issues/571
What follows is a workaround, it is ugly but may help until this issue has been fixed...
Create a new class PHPExcel_Reader_Excel2007_XNamespace.php:
<?php
class PHPExcel_Reader_Excel2007_XNamespace extends PHPExcel_Reader_Excel2007
{
public function securityScan($xml)
{
$xml = parent::securityScan($xml);
return str_replace(['<x:', '</x:'], ['<', '</'], $xml);
}
}
Then load your excel file using this reader:
$excelReader = new PHPExcel_Reader_Excel2007_XNamespace();
$objPHPExcel = $excelReader->load($inputFileName);
UPDATE: A more general issue for handling of non-standard namespaces has been filed and an updated workaround (covering the "d" namespace as well) is found at https://github.com/PHPOffice/PHPExcel/issues/1187#issuecomment-295032648
Related
I'm using PHPSpreadsheet to create Excel.
I want t generate the Excel file, then convert the Excel file in a PDF one.
So I've done the following :
use PhpOffice\PhpSpreadsheet\IOFactory;
use PhpOffice\PhpSpreadsheet\Reader\Xlsx;
use PhpOffice\PhpSpreadsheet\Writer\Pdf\Mpdf;
use PhpOffice\PhpSpreadsheet\Reader\Exception;
class DevisGenerator
{
public function runDevis()
{
$spreadsheet = $this->loadexcelTemplate();
$uuid = $this->uniqidReal();
$filename = $this->writeName($spreadsheet, $uuid);
$this->convertPdf($spreadsheet, $filename);
}
public function writeName($spreadsheet, $uuid)
{
$worksheet = $spreadsheet->getActiveSheet();
$worksheet->getCell('B2')->setValue('Toto');
try {
$writer = IOFactory::createWriter($spreadsheet, 'Xlsx');
$filename = $uuid;
$writer->save($filename.'.xlsx');
}catch (Exception $e)
{
//TODO gestion erreur
}
return $filename;
}
public function convertPdf($spreadsheet, $filename)
{
$writer = new \PhpOffice\PhpSpreadsheet\Writer\Pdf\Mpdf($spreadsheet);
$writer->save($filename.'.pdf');
}
But whan I run the code the following error appear :
Attempted to load class "Mpdf" from namespace "Mpdf".
Did you forget a "use" statement for "PhpOffice\PhpSpreadsheet\Writer\Pdf\Mpdf"?
I did not understand this error, I have correctly insert the use statement in my code.
Any idea ?
I've already got a similar issue with Mpdf.
PHPSpreadsheet supports multiple Librairies to generate PDF.
I'm nut using Mpdf but Tcpdf.
I'm also not sure but you need to install them manually.
composer require tecnickcom/tcpdf
Then in your code :
$writer = new Tcpdf($spreadsheet);
And don't forget the use statement ;)
Hope this help !
After use statement before class, you should use only:
public function convertPdf($spreadsheet, $filename)
{
$writer = new Mpdf($spreadsheet);
$writer->save($filename.'.pdf');
}
Since you use the fully qualified namespace when creating the instance, the use statement is not taken into account (thus the error message).
It seems like you have added a supplementary slash at the beginning of the namespace when creating your Mpdf instance, removing it will solve your issue.
$writer = new PhpOffice\PhpSpreadsheet\Writer\Pdf\Mpdf($spreadsheet);
But since you have added a use statement, you do not need to use the fully qualified namespace again, you can do
$writer = new Mpdf($spreadsheet);
I have installed PhpSpreadsheet and dompdf successfully using composer.
My requirement is that I need to convert an excel sheet into pdf, I got it working using the default settings, this is the code I have used.
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
use PhpOffice\PhpSpreadsheet\Writer\Csv;
use PhpOffice\PhpSpreadsheet\Exception;
use PhpOffice\PhpSpreadsheet\IOFactory;
use \PhpOffice\PhpSpreadsheet\Writer\Pdf\Dompdf;
$spreadsheet = new Spreadsheet();
try {
$sheet = $spreadsheet->getActiveSheet();
// code to fill in the data
$spreadsheet->getActiveSheet()->fromArray(
$data, // The data to set
NULL, // Array values with this value will not be set
'A2' // Top left coordinate of the worksheet range where
);
} catch (Exception $e) {
}
$writer = new Xlsx($spreadsheet);
try {
IOFactory::registerWriter("PDF", Dompdf::class);
$pdfwriter = IOFactory::createWriter($spreadsheet, 'PDF');
$pdfwriter->save($filepath . 'pdf_test.pdf');
} catch (\PhpOffice\PhpSpreadsheet\Writer\Exception $e) {
}
I have skipped out code for brevity, this code works fine and generates a pdf file, I require the pdf to be printed in landscape mode, for that the docs mention a Custom implementation or configuration of the pdf library, so I created a file called PDFBase_DOMPDF that looks like this
use Dompdf\Dompdf;
class PDFBase_DOMPDF extends Dompdf
{
}
And I have created a file called PDFBase_Writer that looks like this.
use PhpOffice\PhpSpreadsheet\Writer\Pdf\Dompdf;
class PDFBase_Writer extends Dompdf
{
protected function createExternalWriterInstance()
{
$instance = new PDFBase_DOMPDF();
$instance->setPaper('A4', 'landscape');
return $instance;
}
}
I modified the original code to use the new pdf class so the line changed to this.
IOFactory::registerWriter("PDF", PDFBase_Writer::class);
The problem is I get an exception with the following error
Registered writers must implement PhpOffice\PhpSpreadsheet\Writer\IWriter
How exactly do I fix this?
Reading and writing to a persisted storage is not possible using the base PhpSpreadsheet classes. For this purpose, PhpSpreadsheet provides readers and writers, which are implementations of \PhpOffice\PhpSpreadsheet\Reader\IReader and \PhpOffice\PhpSpreadsheet\Writer\IWriter.
You must load the Excel file like this:
$reader = new \PhpOffice\PhpSpreadsheet\Reader\Xlsx();
$reader->setReadDataOnly(true);
$spreadsheet = $reader->load("TestRead.xlsx");
Registered writers must implement PhpOffice\PhpSpreadsheet\Writer\IWriter
PHPSpreadsheet Writer classes must implement all the methods defined in the IWriter interface. You're creating a new Writer, so it needs to provide an implementation of all those methods:
interface IWriter
{
/**
* IWriter constructor.
*
* #param Spreadsheet $spreadsheet
*/
public function __construct(Spreadsheet $spreadsheet);
/**
* Save PhpSpreadsheet to file.
*
* #param string $pFilename Name of the file to save
*
* #throws \PhpOffice\PhpSpreadsheet\Writer\Exception
*/
public function save($pFilename);
}
So your Writer needs to implement a constructor that accepts a Spreadsheet object as an argument, and a save() method that accepts a filename (as a string) argument.
I have followed this tutorial here
https://arjunphp.com/how-to-use-phpexcel-with-codeigniter/
and here
http://www.ahowto.net/php/easily-integrateload-phpexcel-into-codeigniter-framework/
However, I still get the PHPExcel_IOFactory not found error. I haven't found much help in this area. Is there an alternative excel plugin or is there a way to solve this?
This is my controllers construct
public function __construct() {
parent::__construct();
$this->load->library('Excel');
}
This is my controllers upload function
$objReader= PHPExcel_IOFactory::createReader('Excel2007');
$objReader->setReadDataOnly(true);
$objPHPExcel= PHPExcel_IOFactory::load($document_folder."/".$file_name.".".$file_extension);
// $this->response($objPHPExcel);
$objPHPExcel=$objReader->load($document_folder."/".$file_name.".".$file_extension);
$totalrows=$objPHPExcel->setActiveSheetIndex(0)->getHighestRow();
$objWorksheet=$objPHPExcel->setActiveSheetIndex(0);
for($i=2;$i<=$totalrows;$i++){ //2 is one row after the header rows
$title= $objWorksheet->getCellByColumnAndRow(0,$i)->getValue();
$first_name= $objWorksheet->getCellByColumnAndRow(1,$i)->getValue();
$last_name= $objWorksheet->getCellByColumnAndRow(2,$i)->getValue();
$date_of_birth= $objWorksheet->getCellByColumnAndRow(3,$i)->getValue();
$email=$objWorksheet->getCellByColumnAndRow(4,$i)->getValue();
$phone_number=$objWorksheet->getCellByColumnAndRow(5,$i)->getValue();
$company=$objWorksheet->getCellByColumnAndRow(6,$i)->getValue();
$input=array(
'title'=>$title,
'first_name'=>$first_name,
'last_name'=>$last_name,
'date_of_birth'=>$date_of_birth,
'email'=>$email,
'phone_number'=>$phone_number,
'company'=>$company,
);
You have to remember that PHPExcel is just PHP, and so is CodeIgniter. You don't have to load PHPExcel through another library. If fact, it doesn't do anything for you. I put all of the PHPExcel files in /third_party/, and then do this:
require_once( APPPATH . 'third_party/PHPExcel-1.8/Classes/PHPExcel.php');
$objReader = PHPExcel_IOFactory::createReader('Excel2007');
Notice that in the example I am using v1.8
If you want to use $objReader in multiple methods, then just make it a class property. So $this->objReader instead.
I am using PHPWord in a project.
I am trying to find out some information about the attributes that go with the $objWriter:
$objWriter = \PhpOffice\PhpWord\IOFactory::createWriter($phpWord, 'Word2007');
Specifically, what is the 'Word2007' used for at the end? I have tried searching $objWriter but can't locate any info. I have tried replacing it with 'Word2013' or 'Word2016' but I get:
"Word2016" is not a valid writer. in wamp...\vendor\phpoffice\phpword\src\PhpWord\IOFactory.php on line 29
The 2nd parameter is for the type of document you're creating a writer for. There are 5 allowed types:
'ODText'
'RTF'
'Word2007'
'HTML'
'PDF'
You can see this in the code for the IOFactory class:
public static function createWriter(PhpWord $phpWord, $name = 'Word2007')
{
/**
* Notice the allowed names in the array here.
*/
if ($name !== 'WriterInterface' && !in_array($name, array('ODText', 'RTF', 'Word2007', 'HTML', 'PDF'), true)) {
throw new Exception("\"{$name}\" is not a valid writer.");
}
$fqName = "PhpOffice\\PhpWord\\Writer\\{$name}";
return new $fqName($phpWord);
}
https://github.com/PHPOffice/PHPWord/blob/develop/src/PhpWord/IOFactory.php
If you want to support a later MS Word document format, you will need to implement your own writer that extends AbstractWriter implements WriterInterface. However, at this time, there are no later formats yet created. (Note that Word 2007 the format is different from the application version.)
I'm relatively new to parsing XML files and am attempting to read a large XML file with XMLReader.
<?xml version="1.0" encoding="UTF-8"?>
<ShowVehicleRemarketing environment="Production" lang="en-CA" release="8.1-Lite" xsi:schemaLocation="http://www.starstandards.org/STAR /STAR/Rev4.2.4/BODs/Standalone/ShowVehicleRemarketing.xsd">
<ApplicationArea>
<Sender>
<Component>Component</Component>
<Task>Task</Task>
<ReferenceId>w5/cron</ReferenceId>
<CreatorNameCode>CreatorNameCode</CreatorNameCode>
<SenderNameCode>SenderNameCode</SenderNameCode>
<SenderURI>http://www.example.com</SenderURI>
<Language>en-CA</Language>
<ServiceId>ServiceId</ServiceId>
</Sender>
<CreationDateTime>CreationDateTime</CreationDateTime>
<Destination>
<DestinationNameCode>example</DestinationNameCode>
</Destination>
</ApplicationArea>
...
I am recieving the following error
ErrorException [ Warning ]: XMLReader::read() [xmlreader.read]: compress.zlib://D:/WebDev/example/local/public/../upload/example.xml.gz:2: namespace error : Namespace prefix xsi for schemaLocation on ShowVehicleRemarketing is not defined
I've searched around and can't find much useful information on using XMLReader to read XML files with namespaces -- How would I go about defining a namespace, if that is in fact what I need to do.. little help? links to pertinent resources?
There needs to be a definition of the xsi namespace. E.g.
<ShowVehicleRemarketing
environment="Production"
lang="en-CA"
release="8.1-Lite"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.starstandards.org/STAR/STAR/Rev4.2.4/BODs/Standalone/ShowVehicleRemarketing.xsd"
>
Update: You could write a user defined filter and then let the XMLReader use that filter, something like:
stream_filter_register('darn', 'DarnFilter');
$src = 'php://filter/read=darn/resource=compress.zlib://something.xml.gz';
$reader->open($src);
The contents read by the compress.zlib wrapper is then "routed" through the DarnFilter which has to find the (first) location where it can insert the xmlns:xsi declaration. But this is quite messy and will take some afford to do it right (e.g. theoretically bucket A could contain xs, bucket B i:schem and bucket C aLocation=")
Update 2: here's an ad-hoc example of a filter in php that inserts the xsi namespace declaration. Mostly untested (worked with the one test I ran ;-) ) and undocumented. Take it as a proof-of-concept not production-code.
<?php
stream_filter_register('darn', 'DarnFilter');
$src = 'php://filter/read=darn/resource=compress.zlib://d:/test.xml.gz';
$r = new XMLReader;
$r->open($src);
while($r->read()) {
echo '.';
}
class DarnFilter extends php_user_filter {
protected $buffer='';
protected $status = PSFS_FEED_ME;
public function filter($in, $out, &$consumed, $closing)
{
while ( $bucket = stream_bucket_make_writeable($in) ) {
$consumed += $bucket->datalen;
if ( PSFS_PASS_ON == $this->status ) {
// we're already done, just copy the content
stream_bucket_append($out, $bucket);
}
else {
$this->buffer .= $bucket->data;
if ( $this->foo() ) {
// first element found
// send the current buffer
$bucket->data = $this->buffer;
$bucket->datalen = strlen($bucket->data);
stream_bucket_append($out, $bucket);
$this->buffer = null;
// no need for further processing
$this->status = PSFS_PASS_ON;
}
}
}
return $this->status;
}
/* looks for the first (root) element in $this->buffer
* if it doesn't contain a xsi namespace decl inserts it
*/
protected function foo() {
$rc = false;
if ( preg_match('!<([^?>\s]+)\s?([^>]*)>!', $this->buffer, $m, PREG_OFFSET_CAPTURE) ) {
$rc = true;
if ( false===strpos($m[2][0], 'xmlns:xsi') ) {
echo ' inserting xsi decl ';
$in = '<'.$m[1][0]
. ' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" '
. $m[2][0] . '>';
$this->buffer = substr($this->buffer, 0, $m[0][1])
. $in
. substr($this->buffer, $m[0][1] + strlen($m[0][0]));
}
}
return $rc;
}
}
Update 3: And here's an ad-hoc solution written in C#
XmlNamespaceManager nsmgr = new XmlNamespaceManager(new NameTable());
// prime the XMLReader with the xsi namespace
nsmgr.AddNamespace("xsi", "http://www.w3.org/2001/XMLSchema-instance");
using ( XmlReader reader = XmlTextReader.Create(
new GZipStream(new FileStream(#"\test.xml.gz", FileMode.Open, FileAccess.Read), CompressionMode.Decompress),
new XmlReaderSettings(),
new XmlParserContext(null, nsmgr, null, XmlSpace.None)
)) {
while (reader.Read())
{
System.Console.Write('.');
}
}
You can file_get_contents and str_replace the XML before passing it to XMLReader.
Either insert the required namespace declararation for the xsi prefix:
$reader = new XMLReader;
$reader->xml(str_replace(
'<ShowVehicleRemarketing',
'<ShowVehicleRemarketing xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"',
file_get_contents('http://example.com/data.xml')));
Another option would be to remove the schemaLocation attribute:
$reader->xml(str_replace(
'xsi:schemaLocation="http://www.starstandards.org/STAR /STAR/Rev4.2.4/BODs/Standalone/ShowVehicleRemarketing.xsd"',
'',
file_get_contents('http://example.com/data.xml')));
However, if there is more prefixes in the document, you will have to replace all of them.
Either fix whatever's writing out malformed XML, or write a separate tool to perform the fix later. (It doesn't have to read it all into memory at the same time, necessarily - stream the data in/out, perhaps reading and writing a line at a time.)
That way your reading code doesn't need to worry about trying to do something useful with the data and fixing it up at the same time.
The xsi namespace is normally reserved for use with Schema Instance Namespace:
xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
if it isn't, your XML file is not XML+NS compliant and cannot be parsed. So you should solve that in the source document.
A note on xsi: it is even more vital than some possible other namespaces, because it directs a validating parser to the correct schema locations for the schema of your XML.