Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Xls and Xlsx readers miss data validation for files saved with Excel #388

Closed
Dfred opened this issue Feb 23, 2018 · 13 comments
Closed

Xls and Xlsx readers miss data validation for files saved with Excel #388

Dfred opened this issue Feb 23, 2018 · 13 comments
Labels

Comments

@Dfred
Copy link

Dfred commented Feb 23, 2018

This is:

- [X] a bug report
- [ ] a feature request
- [ ] **not** a usage question (ask them on https://stackoverflow.com/questions/tagged/phpspreadsheet or https://gitter.im/PHPOffice/PhpSpreadsheet)

What is the expected behavior?

Readers should be able to read cells' DataValidation info from .xls and .xlsx files wether or not they are generated by MS-Excel or PhpSpreadsheet.

What is the current behavior?

Currently, xls and xlsx files generated with PhpSpreadsheet, then opened and saved with MS-Excel are missing all cross-sheet DataValidation of the form "sheet_name!ref:ref" .
In this case, calling getDataValidationCollection() will only return correct DataValidation for cells in the target sheet.

What are the steps to reproduce?

<?php

require __DIR__ . '/vendor/autoload.php';

function test_save(
  $fileFormat   = 'Xls',                  // change to .Xlsx to make this example work.
  $test_cellRef = 'A1',
  $fileName     = 'dataValidationBug.' )
{
  $fileName .= strtolower($fileFormat);
  
  // Create new Spreadsheet object with 2 cells in 2nd sheet, then create a dropDown in each sheet. 
  $spreadsheet = new \PhpOffice\PhpSpreadsheet\Spreadsheet();
  $worksheet1 = $spreadsheet->getActiveSheet();
  $worksheet2 = $spreadsheet->createSheet()->setTitle('Worksheet2');
  $worksheet3 = $spreadsheet->createSheet()->setTitle('Worksheet3');

  $worksheet2->getCell('B1')->setValue(5);
  $worksheet2->getCell('B2')->setValue(6);
  foreach ($spreadsheet->getWorksheetIterator() as $ws) {
    $ws->getCell($test_cellRef)->getDataValidation()->
          setType(\PhpOffice\PhpSpreadsheet\Cell\DataValidation::TYPE_LIST)->
          setFormula1($worksheet2->getTitle() . '!B1:B2')->
          setShowDropDown(TRUE);
    $ws->getCell($test_cellRef.'0')->getDataValidation()->
          setType(\PhpOffice\PhpSpreadsheet\Cell\DataValidation::TYPE_LIST)->
          setFormula1($worksheet2->getTitle() . '!$B1:$B2')->
          setShowDropDown(TRUE);
  }
  // save the file.
  $writerClass = "\\PhpOffice\\PhpSpreadsheet\\Writer\\$fileFormat";
  $writer = new $writerClass($spreadsheet);
  $writer->save($fileName);               // the file is as expected with Excel.
  echo ">>> wrote\t $fileName with ".get_class($writer)." ===\n";
}

// if loading an Xls file saved by PhpSpreadsheet, all data is read as expected.
// if loading an Xls file saved by Microsoft Excel, it warns of dropped functionality: all cross-sheet refs in validation are discarded. 
// if loading an Xlsx file saved by PhpSpreadsheet, all data is read as expected.
// if loading an Xlsx file saved by Microsoft Excel, all cross-sheet refs in validation are ignored. 
function test_load(
  $fileFormat   = 'Xls',                  // change to .Xlsx to make this example work.
  $test_cellRef = 'A1',
  $fileName     = 'dataValidationBug.' )
{
  $fileName .= strtolower($fileFormat);
  echo "\n<<< reading\t $fileName ===\n";
  foreach (\PhpOffice\PhpSpreadsheet\IOFactory::load($fileName)->getWorksheetIterator() as $ws) {
    echo "\n - sheet {$ws->getTitle()} - ";
    echo "DataValidationCollection: "; var_dump($ws->getDataValidationCollection());
  }
}

// run the test with Xls format
test_save();
test_load();

// run the test with Xls format
test_save('Xlsx');
test_load('Xlsx');

Which versions of PhpSpreadsheet and PHP are affected?

development branch, php 5.6, MS Excel Office 2010

@PowerKiKi
Copy link
Member

Could you suggest a PR for that ?

@Dfred
Copy link
Author

Dfred commented Feb 26, 2018

that's planned, but can't do it before April!

@Dfred
Copy link
Author

Dfred commented Mar 12, 2018

So the error comes from simplexml_load_string() failing to read Microsoft extensions (see https://msdn.microsoft.com/en-us/library/dd905242(v=office.12).aspx)

In my case the xml looks like this:

<extLst>
  <ext uri="{CCE6A557-97BC-4b89-ADB6-D9C93CAAB3DF}" xmlns:x14="http://schemas.microsoft.com/office/spreadsheetml/2009/9/main">
    <x14:dataValidations count="1" xmlns:xm="http://schemas.microsoft.com/office/excel/2006/main">
      <x14:dataValidation type="list" operator="equal" allowBlank="1" showErrorMessage="1">
        <x14:formula1>
          <xm:f>myWorksheet1!$A$13:$C$13</xm:f>
        </x14:formula1>
        <xm:sqref>E4:F4</xm:sqref>
      </x14:dataValidation>
    </x14:dataValidations>
  </ext>
</extLst>
</worksheet>

The call only loads the uri attribute of the ext element.

Dfred pushed a commit to Dfred/PhpSpreadsheet that referenced this issue Mar 14, 2018
Dfred pushed a commit to Dfred/PhpSpreadsheet that referenced this issue Mar 14, 2018
Dfred pushed a commit to Dfred/PhpSpreadsheet that referenced this issue Mar 14, 2018
…cEngine_errors' and 'PHPOffice#388-Xlsx_MSextensions' into develop-merges
@stale
Copy link

stale bot commented May 11, 2018

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs.
If this is still an issue for you, please try to help by debugging it further and sharing your results.
Thank you for your contributions.

@stale stale bot added the stale label May 11, 2018
@stale stale bot closed this as completed May 18, 2018
billblume added a commit to Fonemine/PhpSpreadsheet that referenced this issue Jun 15, 2018
@billblume
Copy link
Contributor

FYI. I am still encountering this issue. The provided fix and pull request does work for me.

@PowerKiKi
Copy link
Member

The PR still misses tests to be merged, consider contributing some if you can.

@diego-sorribas
Copy link

I think there is a problem with that code, if I dont have data validations on the same sheet it not going to enter in the if condition. I think that it must be outside of the if condition on line 1337.

@Dfred
Copy link
Author

Dfred commented Aug 20, 2018

Can you provide more context (branch, commit ID, ...) about the code you're mentioning, as well as a test for this case?

@diego-sorribas
Copy link

Im talking about this line

I think this works if you dont have data validation in the same sheet

<?php
if (isset ($xmlSheet->extLst, $xmlSheet->extLst->ext, $xmlSheet->extLst->ext['uri'])
	 && $xmlSheet->extLst->ext['uri'] == "{CCE6A557-97BC-4b89-ADB6-D9C93CAAB3DF}" ) {

// Create dataValidations node if does not exists, maybe is better inside the foreach ?
if(!$xmlSheet->dataValidations) {
	$xmlSheet->addChild('dataValidations');
}

foreach ($xmlSheet->extLst->ext->children('x14', TRUE)->dataValidations->dataValidation as $item) {
	$node = $xmlSheet->dataValidations->addChild('dataValidation');
    foreach ($item->attributes() as $attr) {
    	$node->addAttribute($attr->getName(), $attr);
        $node->addAttribute('sqref', $item->children('xm',TRUE)->sqref);
        $node->addChild('formula1', $item->formula1->children('xm',TRUE)->f);
	}
  }
}

if ($xmlSheet && $xmlSheet->dataValidations && !$this->readDataOnly) {
 // Continue PhpSpreadhseet code....
 }

Dfred pushed a commit to Dfred/PhpSpreadsheet that referenced this issue Oct 27, 2018
Dfred pushed a commit to Dfred/PhpSpreadsheet that referenced this issue Nov 20, 2018
Dfred pushed a commit to Dfred/PhpSpreadsheet that referenced this issue Jan 27, 2019
…ataValidations even if only present as MS extensions.
@imrohan22
Copy link

imrohan22 commented Jan 28, 2019

Make sure there is no space in your Sheet name in setFormula1 function call, otherwise this fix won't work.

@nooraldeen-albakri
Copy link

Im talking about this line

I think this works if you dont have data validation in the same sheet

<?php
if (isset ($xmlSheet->extLst, $xmlSheet->extLst->ext, $xmlSheet->extLst->ext['uri'])
	 && $xmlSheet->extLst->ext['uri'] == "{CCE6A557-97BC-4b89-ADB6-D9C93CAAB3DF}" ) {

// Create dataValidations node if does not exists, maybe is better inside the foreach ?
if(!$xmlSheet->dataValidations) {
	$xmlSheet->addChild('dataValidations');
}

foreach ($xmlSheet->extLst->ext->children('x14', TRUE)->dataValidations->dataValidation as $item) {
	$node = $xmlSheet->dataValidations->addChild('dataValidation');
    foreach ($item->attributes() as $attr) {
    	$node->addAttribute($attr->getName(), $attr);
        $node->addAttribute('sqref', $item->children('xm',TRUE)->sqref);
        $node->addChild('formula1', $item->formula1->children('xm',TRUE)->f);
	}
  }
}

if ($xmlSheet && $xmlSheet->dataValidations && !$this->readDataOnly) {
 // Continue PhpSpreadhseet code....
 }

thanks, worked successfully.

@gil04
Copy link

gil04 commented Aug 8, 2019

Did someone created a pull request for this? Cause with the actual version of PHPOffice/PhpSpreadsheet this code is not included and I also still have this problem.

@kgbph
Copy link

kgbph commented Dec 2, 2020

Any updates about this?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Development

No branches or pull requests

8 participants