Workaround for copying style with PHPExcel - phpexcel

I want to copy the style information from cells to ranges, like Format Painter in Excel. The documentation says to do something like this:
$activeSheet->duplicateStyle($activeSheet->getStyle('A1'), 'D1:D100');
$activeSheet->duplicateStyle($activeSheet->getStyle('B1'), 'E1:E100');
There appears to be a bug because both D1:D100 and E1:E100 get the style from cell B1. If I change the order of the two lines, both ranges get the style from A1. Similarly,
$styleA = $activeSheet->getStyle('A1');
$styleB = $activeSheet->getStyle('B1');
$activeSheet->duplicateStyle($styleA, 'D1:D100');
results in D1:D100 getting style info from cell B1. The last getStyle value is used in all duplicateStyle results.
I'm sure that a future release of PHPExcel will have a fix, I just need to figure out a work-around until then.

One workround for you might be to use the style xf Indexes:
$xfIndex = $activeSheet->getCell('A1')->getXfIndex();
Then to set that value for the xfIndex of all cells in the range
for ($col = 'D'; $col != 'E'; ++$col) {
for ($row = 1; $row <= 100; ++$row) {
$activeSheet->getCell($col . $row)->setXfIndex($xfIndex);
}
}
EDIT
Alternatively, apply fix to the duplicateStyle() method in Classes/PHPExcel/Worksheet.php
lines 1479 to 1486 currently read:
if ($this->_parent->cellXfExists($pCellStyle)) {
// there is already this cell Xf in our collection
$xfIndex = $pCellStyle->getIndex();
} else {
// we don't have such a cell Xf, need to add
$workbook->addCellXf($pCellStyle);
$xfIndex = $pCellStyle->getIndex();
}
change to:
if ($existingStyle = $this->_parent->getCellXfByHashCode($pCellStyle->getHashCode())) {
// there is already such cell Xf in our collection
$xfIndex = $existingStyle->getIndex();
} else {
// we don't have such a cell Xf, need to add
$workbook->addCellXf($pCellStyle);
$xfIndex = $pCellStyle->getIndex();
}
Similarly in the applyFromArray() method in Classes/PHPExcel/Style.php
lines 425 to 432 currently read:
if ($workbook->cellXfExists($newStyle)) {
// there is already such cell Xf in our collection
$newXfIndexes[$oldXfIndex] = $existingStyle->getIndex();
} else {
// we don't have such a cell Xf, need to add
$workbook->addCellXf($newStyle);
$newXfIndexes[$oldXfIndex] = $newStyle->getIndex();
}
change to:
if ($existingStyle = $workbook->getCellXfByHashCode($newStyle->getHashCode())) {
// there is already such cell Xf in our collection
$newXfIndexes[$oldXfIndex] = $existingStyle->getIndex();
} else {
// we don't have such a cell Xf, need to add
$workbook->addCellXf($newStyle);
$newXfIndexes[$oldXfIndex] = $newStyle->getIndex();
}
EDIT #2
Fix has now been pushed to the develop branch on github. It does give a slight performance hit, depending on the number of styles in use... I'll try and get a faster version tomorrow night

Related

How to query exactly selected items in Paper.js?

According to my understanding, project.getItems({selected: true}) returns wrong results: I'm selecting a curve, it returns the parent Path: Sketch
Try clicking on a curve or a segment. Whole path will be moved. Then try changing the behavior by setting var workaround = false to var workaround = true to observe desired behavior.
How can I get exactly what is really selected?
Current workaround
I'm currently adding those objects into an array on selection and use those items instead of project.getItems({selected: true}).
The thing is that in Paper.js architecture, curves and segments are not items (they are part of a specific item which is the path). So you shouldn't expect project.getItems() to return anything else than items.
Another thing you have to know is that a path is assumed selected if any of its part is selected (curves, segments, points, handles, position, bounds, ...). And a curve is assumed selected if all of its parts are selected (points and handles).
With that in mind, you can create an algorithm to retrieve "what is really selected" based on project.getItems({selected: true}) as its first part. Then, you need to loop through curves and segments to check if they are selected.
Here is a sketch demonstrating a possible solution.
var vector = new Point(10, 10);
// Create path.
var path = new Path({
segments: [
[100, 100],
[200, 100],
[260, 170],
[360, 170],
[420, 250]
],
strokeColor: 'red',
strokeWidth: 10
});
// Translate given thing along global vector.
function translateThing(thing) {
switch (thing.getClassName()) {
case 'Path':
thing.position += vector;
break;
case 'Curve':
thing.segment1.point += vector;
thing.segment2.point += vector;
break;
case 'Segment':
thing.point += vector;
break;
}
}
// On mouse down...
function onMouseDown(event) {
// ...only select what was clicked.
path.selected = false;
hit = paper.project.hitTest(event.point);
if (hit && hit.location) {
hit.location.curve.selected = true;
}
else if (hit && hit.segment) {
hit.segment.selected = true;
}
// We check all items for demo purpose.
// Move all selected things.
// First get selected items in active layer...
project.activeLayer.getItems({ selected: true })
// ...then map them to what is really selected...
.map(getSelectedThing)
// ...then translate them.
.forEach(translateThing);
}
// This method returns what is really selected in a given item.
// Here we assume that only one thing can be selected at the same time.
// Returned thing can be either a Curve, a Segment or an Item.
function getSelectedThing(item) {
// Only check curves and segments if item is a path.
if (item.getClassName() === 'Path') {
// Check curves.
for (var i = 0, l = item.curves.length; i < l; i++) {
if (item.curves[i].selected) {
return item.curves[i];
}
}
// Check segments.
for (var i = 0, l = item.segments.length; i < l; i++) {
if (item.segments[i].selected) {
return item.segments[i];
}
}
}
// return item by default.
return item;
}
That said, depending on your real use case, your current workaround could be more appropriate than this approach.

PHPExcel Can't copy Style object

I want to copy cells styles to an array and than use
$arr = array();
$arr[] = $PHPExcel->getActiveSheet()->getStyle('A1');
$arr[] = $PHPExcel->getActiveSheet()->getStyle('B1');
$arr[] = $PHPExcel->getActiveSheet()->getStyle('C1');
//do smth ....
$PHPExcel->getActiveSheet()->duplicateStyle($arr[0],'A2');
$PHPExcel->getActiveSheet()->duplicateStyle($arr[1],'B2');
$PHPExcel->getActiveSheet()->duplicateStyle($arr[2],'C2');
But all cells A2,B2,C2 get the same style as C1.
What's wrong?
Solution 1: Please use the latest version of PHPExcel from github
Solution 2:
Manually apply fix to the duplicateStyle() method in Classes/PHPExcel/Worksheet.php
from:
if ($this->_parent->cellXfExists($pCellStyle)) {
// there is already this cell Xf in our collection
$xfIndex = $pCellStyle->getIndex();
} else {
// we don't have such a cell Xf, need to add
$workbook->addCellXf($pCellStyle);
$xfIndex = $pCellStyle->getIndex();
}
change to:
if ($existingStyle = $this->_parent->getCellXfByHashCode($pCellStyle-
>getHashCode())) {
// there is already such cell Xf in our collection
$xfIndex = $existingStyle->getIndex();
} else {
// we don't have such a cell Xf, need to add
$workbook->addCellXf($pCellStyle);
$xfIndex = $pCellStyle->getIndex();
}

update edited cells within treetable after expand/collapse items

I have a treeTable with editable cells within the expanded rows. The editable cells get a dirty flag after editing (in the example the background color is set to red).
The problem i'm running into is that i found no certain way to update the dirty flag on expand/collapse (edited cells get the css class 'edited-cell').
At the moment the code looks like that:
// each editable textfield gets a Listener
textField.attachLiveChange(
var source = oEvent.oSource;
...
jQuery('#' + source.getId()).addClass(EDITED_CELL_CLASS, false)
// list with cell ids, e.g. "__field1-col1-row1"
DIRTY_MODELS.push(model.getId()) //*** add also binding context of row
)
// the table rows are updated on toggleOpenState
new sap.ui.table.TreeTable({
toggleOpenState: function(oEvent) {
...
this.updateRows() // see function below
}
})
// update Rows function is also delegated
oTable.addDelegate({ onAfterRendering : jQuery.proxy(this.updateRows, oTable)});
//http://stackoverflow.com/questions/23683627/access-row-for-styling-in-sap-ui5-template-handler-using-jquery
// this method is called on each expand/collapse: here i can make sure that the whole row has it's correct styling...
// but how to make sure that special cells are dirty?
function updateRows(oEvent) {
if (oEvent.type !== 'AfterRendering'){
this.onvscroll(oEvent);
}
var rows = this.getVisibleRowCount();
var rowStart = this.getFirstVisibleRow();
var actualRow;
for (var i = 0; i < rows; i++){
actualRow = this.getContextByIndex(rowStart + i); //content
var row = this.getRows()[i]
var obj = actualRow.getObject()
var rowId = row.getId()
updateStyleOfRows(obj, rowId, actualRow)
updateDirtyCells(rowId) //*** how to get the binding context in this function???
}
};
// update Dirty Cells in function updateRows():
function updateDirtyCells(rowId){
for (var i = 0; i < DIRTY_MODELS.length; i++){
var dirtyCellId = DIRTY_MODELS[i]
//*** make sure that only the correct expanded/collapsed rows will be updated -> depends on the bindingContext of the row
jQuery('#' + rowId).find('#' + dirtyCellId + '.editable-cell').addClass(EDITED_CELL_CLASS, false)
}
}
This doesn't work correctly, because the ids of the cells change on each layout render (e.g. collapse/expand rows). Please see attached image.
Let me know if i should provide more information.

phpexcel PHPExcel_Shared_Date::isDateTime not working

the file is xlsx and the column format is date MM-DD-YYYY
I have tried several different ways to determine if the value is a date.
PHPExcel_Shared_Date::isDateTime just is not working and do not know why. The data is being save in database and is showing the numbers as such:
41137
41618
42206
42076
41137
42206
41137
41988
my code:
$inputFileType = PHPExcel_IOFactory::identify($fullFilePath);
$objReader = PHPExcel_IOFactory::createReader($inputFileType);
$objReader->setReadDataOnly(false);
$objPHPExcel = $objReader->load($fullFilePath);
$objPHPExcel->setActiveSheetIndex(0);
$worksheetIndex = 1;
$worksheetName = '';
$actualRows = 0;
foreach($objPHPExcel->getWorksheetIterator() as $worksheet)
{
$lineNumber = 1;
$worksheetName = $worksheet->getTitle();
$columnSum = array();
foreach($worksheet->getRowIterator() as $row)
{
$cellIterator = $row->getCellIterator();
$cellIterator->setIterateOnlyExistingCells(true); // Loop all cells, even if it is not set = true else set to false
$columnNumber = 1;
foreach($cellIterator as $cell)
{
$dataValue = $cell->getCalculatedValue();
//$dataValue = $cell->getFormattedValue();
if(!empty($dataValue))
{
if(PHPExcel_Shared_Date::isDateTime($cell))
{
$dataValue = date('Y-m-d H', PHPExcel_Shared_Date::ExcelToPHP($dataValue));
}
else
{
// do something
}
}
}
}
}
Analysing the file, there's a few problems.... it doesn't validate cleanly under the Microsoft's Open XML SDK 2.0 productivity tool for MS Office.
Initial problem (which should trigger a loader error in PHPExcel) is the SheetView Zoom Scale, which should be a minimum value of 1. This problem can by "bypassed" by editing Classes/PHPExcel/Worksheet/SheetView.php and modifying the setZoomScaleNormal() method to avoid throwing the exception if the supplied argument value is out of range.
public function setZoomScaleNormal($pValue = 100)
{
if (($pValue >= 1) || is_null($pValue)) {
$this->zoomScaleNormal = $pValue;
// } else {
// throw new PHPExcel_Exception("Scale must be greater than or equal to 1.");
}
return $this;
}
The second problem is that custom number formats are defined with ids in the range 100-118, but all format ids below 164 are documented as reserved for Microsoft use. Excel itself is obviously more forgiving about breaking its documented rules.
You can resolved this by hacking the Classes/PHPExcel/Reader/Excel2007.php file and modifying the load() method, around line 512, by commenting out the check that tells PHPExcel to use the built-in number formats:
// if ((int)$xf["numFmtId"] < 164) {
// $numFmt = PHPExcel_Style_NumberFormat::builtInFormatCode((int)$xf["numFmtId"]);
// }
or
if ((int)$xf["numFmtId"] < 164 &&
PHPExcel_Style_NumberFormat::builtInFormatCodeIndex((int)$xf["numFmtId"]) !== false) {
$numFmt = PHPExcel_Style_NumberFormat::builtInFormatCode((int)$xf["numFmtId"]);
}
There are also issues with font size definitions, but these won't prevent the file from loading

PHPexcel - getOldCalculatedValue and rangeToArray

Searched for quite a while now, but I'm stuck at the following problem.
I am using PHPexcel 1.8.0
The spreadsheet is read using the following code:
$rowData = $sheet->rangeToArray('A' . $row . ':' . $highestColumn . $row, NULL, TRUE, TRUE);
So far ok and it works well.
But some spreadsheets contain external referenced data.
And for that I want to use "getOldCalculatedValue".
How do I combine "getOldCalculatedValue" with "rangeToArray" ?
Or is "rangeToArray" inappropriate for this ?
Thanks for any help or hints !
Simple answer, you can't combine the two
rangeToArray() is a simple method for a simple purpose, it doesn't try to do anything clever, simply to return the data from the worksheet as efficiently and quickly as possible
getOldCalculatedValue() is used for a very specific circumstance, and isn't guaranteed to be correct even then, because it retrieves the last value calculated for the cell in MS EXcel itself, which ,ay not be correct if the external workbook wasn't available to MS Excel in that circumstance, or MS Excel formula evaluation was disable.
When calculating cells values from a formula, the PHPExcel calculation engine should use the getOldCalculatedValue() as a fallback if it finds an external reference, and rangeToArray() will try to use this method, but it isn't perfect, especially when that reference in nested deep inside other formulae referenced in other cells.
If you know that a formula in a cell contains an external reference, you should use getOldCalculatedValue() directly for that cell
I came up with the following solution.
Maybe not perfect, but it currently does the job. Thanks for any improvements!
With PHPExcel included and the excel file uploaded and ready, I continue with:
$sheet = $objPHPExcel->getSheet(0);
$highestRow = $sheet->getHighestRow();
Create a new array to store the cell values of a row
$arr_row = array();
Loop through the rows
for ($rownumber = 2; $rownumber <= $highestRow; $rownumber++){
$row = $sheet->getRowIterator($rownumber)->current();
$cellIterator = $row->getCellIterator();
$cellIterator->setIterateOnlyExistingCells(false);
Then loop through the cells of the current row
foreach ($cellIterator as $cell) {
Find cells with a formula
$cellcheck = substr($cell->getValue(),0,1);
if($cellcheck == '='){
$cell_content = $cell->getOldCalculatedValue();
}
else{
$cell_content = $cell->getValue();
}
Add the cell values to the array
array_push($arr_row,$cell_content);
Close cell loop
}
At this point I use the $arr_row to do further calculations and string formatting, before finally inserting it into a mysql table.
Close row loop
}
I made some changes in the function rangeToArray() inside Worksheet.php.
Worked fine!
public function rangeToArray($pRange = 'A1', $nullValue = null, $calculateFormulas = true, $formatData = true, $returnCellRef = false) {
// Returnvalue
$returnValue = array();
// Identify the range that we need to extract from the worksheet
list($rangeStart, $rangeEnd) = PHPExcel_Cell::rangeBoundaries($pRange);
$minCol = PHPExcel_Cell::stringFromColumnIndex($rangeStart[0] -1);
$minRow = $rangeStart[1];
$maxCol = PHPExcel_Cell::stringFromColumnIndex($rangeEnd[0] -1);
$maxRow = $rangeEnd[1];
$maxCol++;
// Loop through rows
$r = -1;
for ($row = $minRow; $row <= $maxRow; ++$row) {
$rRef = ($returnCellRef) ? $row : ++$r;
$c = -1;
// Loop through columns in the current row
for ($col = $minCol; $col != $maxCol; ++$col) {
$cRef = ($returnCellRef) ? $col : ++$c;
// Using getCell() will create a new cell if it doesn't already exist. We don't want that to happen
// so we test and retrieve directly against _cellCollection
if ($this->_cellCollection->isDataSet($col.$row)) {
// Cell exists
$cell = $this->_cellCollection->getCacheData($col.$row);
if ($cell->getValue() !== null) {
if ($cell->getValue() instanceof PHPExcel_RichText) {
$returnValue[$rRef][$cRef] = $cell->getValue()->getPlainText();
} else {
if ($calculateFormulas)
{ ##################################### CHANGED LINES
if(!preg_match('/^[=].*/', $cell->getValue()))
{
$returnValue[$rRef][$cRef] = $cell->getCalculatedValue(); # THE ORIGINAL CODE ONLY HAD THIS LINE
}
else
{
$returnValue[$rRef][$cRef] = $cell->getOldCalculatedValue();
}
} ##################################### CHANGED LINES
else
{
$returnValue[$rRef][$cRef] = $cell->getValue();
}
}
if ($formatData) {
$style = $this->_parent->getCellXfByIndex($cell->getXfIndex());
$returnValue[$rRef][$cRef] = PHPExcel_Style_NumberFormat::toFormattedString(
$returnValue[$rRef][$cRef],
($style && $style->getNumberFormat()) ?
$style->getNumberFormat()->getFormatCode() :
PHPExcel_Style_NumberFormat::FORMAT_GENERAL
);
}
} else {
// Cell holds a NULL
$returnValue[$rRef][$cRef] = $nullValue;
}
} else {
// Cell doesn't exist
$returnValue[$rRef][$cRef] = $nullValue;
}
}
}
// Return
return $returnValue;
}

Resources