Skip to content

Commit 83c759e

Browse files
SailorMaxPowerKiKi
authored andcommitted
Support to read and write unsupported data for XLSX
This will let users read a file that contains data that are not properly supported and write them back to a new file untouched. - load workbookProtection attributes - save loaded pageSetup[r:id] - save loaded sheet's AlternateContent - save loaded unparsed VmlDrawings - save loaded drawing files `rId` - save loaded draw's AlternateContent - save loaded control properties - save loaded printer settings - save loaded unparsed override content types (for ctrlProp, ...) Closes #435
1 parent 064076a commit 83c759e

File tree

10 files changed

+474
-45
lines changed

10 files changed

+474
-45
lines changed

CHANGELOG.md

+6-1
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,14 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
77

88
## [Unreleased]
99

10+
11+
### Added
12+
13+
- Support to read Xlsm templates with form elements, macros, printer settings, protected elements and back compatibility drawing, and save result without losing important elements of document - [#435](https://github.com/PHPOffice/PhpSpreadsheet/issues/435)
14+
1015
### Fixed
1116

12-
- GH-332 Subtotal Calculation
17+
- Subtotal 9 in a group that has other subtotals 9 exclude the totals of the other subtotals in the range - [#332](https://github.com/PHPOffice/PhpSpreadsheet/issues/332)
1318

1419
## [1.2.1] - 2018-04-10
1520

src/PhpSpreadsheet/Reader/Xlsx.php

+205-30
Large diffs are not rendered by default.

src/PhpSpreadsheet/Spreadsheet.php

+34
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,14 @@ class Spreadsheet
115115
*/
116116
private $ribbonBinObjects;
117117

118+
/**
119+
* List of unparsed loaded data for export to same format with better compatibility.
120+
* It has to be minimized when the library start to support currently unparsed data.
121+
*
122+
* @var array
123+
*/
124+
private $unparsedLoadedData = [];
125+
118126
/**
119127
* The workbook has macros ?
120128
*
@@ -256,6 +264,32 @@ public function setRibbonBinObjects($BinObjectsNames, $BinObjectsData)
256264
}
257265
}
258266

267+
/**
268+
* List of unparsed loaded data for export to same format with better compatibility.
269+
* It has to be minimized when the library start to support currently unparsed data.
270+
*
271+
* @internal
272+
*
273+
* @return array
274+
*/
275+
public function getUnparsedLoadedData()
276+
{
277+
return $this->unparsedLoadedData;
278+
}
279+
280+
/**
281+
* List of unparsed loaded data for export to same format with better compatibility.
282+
* It has to be minimized when the library start to support currently unparsed data.
283+
*
284+
* @internal
285+
*
286+
* @param array $unparsedLoadedData
287+
*/
288+
public function setUnparsedLoadedData(array $unparsedLoadedData)
289+
{
290+
$this->unparsedLoadedData = $unparsedLoadedData;
291+
}
292+
259293
/**
260294
* return the extension of a filename. Internal use for a array_map callback (php<5.3 don't like lambda function).
261295
*

src/PhpSpreadsheet/Writer/Xlsx.php

+24-1
Original file line numberDiff line numberDiff line change
@@ -290,12 +290,25 @@ public function save($pFilename)
290290
}
291291
}
292292

293-
$chartRef1 = $chartRef2 = 0;
293+
$chartRef1 = 0;
294294
// Add worksheet relationships (drawings, ...)
295295
for ($i = 0; $i < $this->spreadSheet->getSheetCount(); ++$i) {
296296
// Add relationships
297297
$zip->addFromString('xl/worksheets/_rels/sheet' . ($i + 1) . '.xml.rels', $this->getWriterPart('Rels')->writeWorksheetRelationships($this->spreadSheet->getSheet($i), ($i + 1), $this->includeCharts));
298298

299+
// Add unparsedLoadedData
300+
$sheetCodeName = $this->spreadSheet->getSheet($i)->getCodeName();
301+
if (isset($this->spreadSheet->getUnparsedLoadedData()['sheets'][$sheetCodeName]['ctrlProps'])) {
302+
foreach ($this->spreadSheet->getUnparsedLoadedData()['sheets'][$sheetCodeName]['ctrlProps'] as $ctrlProp) {
303+
$zip->addFromString($ctrlProp['filePath'], $ctrlProp['content']);
304+
}
305+
}
306+
if (isset($this->spreadSheet->getUnparsedLoadedData()['sheets'][$sheetCodeName]['printerSettings'])) {
307+
foreach ($this->spreadSheet->getUnparsedLoadedData()['sheets'][$sheetCodeName]['printerSettings'] as $ctrlProp) {
308+
$zip->addFromString($ctrlProp['filePath'], $ctrlProp['content']);
309+
}
310+
}
311+
299312
$drawings = $this->spreadSheet->getSheet($i)->getDrawingCollection();
300313
$drawingCount = count($drawings);
301314
if ($this->includeCharts) {
@@ -307,6 +320,9 @@ public function save($pFilename)
307320
// Drawing relationships
308321
$zip->addFromString('xl/drawings/_rels/drawing' . ($i + 1) . '.xml.rels', $this->getWriterPart('Rels')->writeDrawingRelationships($this->spreadSheet->getSheet($i), $chartRef1, $this->includeCharts));
309322

323+
// Drawings
324+
$zip->addFromString('xl/drawings/drawing' . ($i + 1) . '.xml', $this->getWriterPart('Drawing')->writeDrawings($this->spreadSheet->getSheet($i), $this->includeCharts));
325+
} elseif (isset($this->spreadSheet->getUnparsedLoadedData()['sheets'][$sheetCodeName]['drawingAlternateContents'])) {
310326
// Drawings
311327
$zip->addFromString('xl/drawings/drawing' . ($i + 1) . '.xml', $this->getWriterPart('Drawing')->writeDrawings($this->spreadSheet->getSheet($i), $this->includeCharts));
312328
}
@@ -320,6 +336,13 @@ public function save($pFilename)
320336
$zip->addFromString('xl/comments' . ($i + 1) . '.xml', $this->getWriterPart('Comments')->writeComments($this->spreadSheet->getSheet($i)));
321337
}
322338

339+
// Add unparsed relationship parts
340+
if (isset($this->spreadSheet->getUnparsedLoadedData()['sheets'][$this->spreadSheet->getSheet($i)->getCodeName()]['vmlDrawings'])) {
341+
foreach ($this->spreadSheet->getUnparsedLoadedData()['sheets'][$this->spreadSheet->getSheet($i)->getCodeName()]['vmlDrawings'] as $vmlDrawing) {
342+
$zip->addFromString($vmlDrawing['filePath'], $vmlDrawing['content']);
343+
}
344+
}
345+
323346
// Add header/footer relationship parts
324347
if (count($this->spreadSheet->getSheet($i)->getHeaderFooter()->getImages()) > 0) {
325348
// VML Drawings

src/PhpSpreadsheet/Writer/Xlsx/ContentTypes.php

+18-2
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,8 @@ public function writeContentTypes(Spreadsheet $spreadsheet, $includeCharts = fal
5757
// Yes : not standard content but "macroEnabled"
5858
$this->writeOverrideContentType($objWriter, '/xl/workbook.xml', 'application/vnd.ms-excel.sheet.macroEnabled.main+xml');
5959
//... and define a new type for the VBA project
60-
$this->writeDefaultContentType($objWriter, 'bin', 'application/vnd.ms-office.vbaProject');
60+
// Better use Override, because we can use 'bin' also for xl\printerSettings\printerSettings1.bin
61+
$this->writeOverrideContentType($objWriter, '/xl/vbaProject.bin', 'application/vnd.ms-office.vbaProject');
6162
if ($spreadsheet->hasMacrosCertificate()) {
6263
// signed macros ?
6364
// Yes : add needed information
@@ -93,9 +94,10 @@ public function writeContentTypes(Spreadsheet $spreadsheet, $includeCharts = fal
9394
$drawings = $spreadsheet->getSheet($i)->getDrawingCollection();
9495
$drawingCount = count($drawings);
9596
$chartCount = ($includeCharts) ? $spreadsheet->getSheet($i)->getChartCount() : 0;
97+
$hasUnparsedDrawing = isset($spreadsheet->getUnparsedLoadedData()['sheets'][$spreadsheet->getSheet($i)->getCodeName()]['drawingOriginalIds']);
9698

9799
// We need a drawing relationship for the worksheet if we have either drawings or charts
98-
if (($drawingCount > 0) || ($chartCount > 0)) {
100+
if (($drawingCount > 0) || ($chartCount > 0) || $hasUnparsedDrawing) {
99101
$this->writeOverrideContentType($objWriter, '/xl/drawings/drawing' . ($i + 1) . '.xml', 'application/vnd.openxmlformats-officedocument.drawing+xml');
100102
}
101103

@@ -160,6 +162,20 @@ public function writeContentTypes(Spreadsheet $spreadsheet, $includeCharts = fal
160162
}
161163
}
162164

165+
// unparsed defaults
166+
if (isset($spreadsheet->getUnparsedLoadedData()['default_content_types'])) {
167+
foreach ($spreadsheet->getUnparsedLoadedData()['default_content_types'] as $extName => $contentType) {
168+
$this->writeDefaultContentType($objWriter, $extName, $contentType);
169+
}
170+
}
171+
172+
// unparsed overrides
173+
if (isset($spreadsheet->getUnparsedLoadedData()['override_content_types'])) {
174+
foreach ($spreadsheet->getUnparsedLoadedData()['override_content_types'] as $partName => $overrideType) {
175+
$this->writeOverrideContentType($objWriter, $partName, $overrideType);
176+
}
177+
}
178+
163179
$objWriter->endElement();
164180

165181
// Return

src/PhpSpreadsheet/Writer/Xlsx/Drawing.php

+7
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,13 @@ public function writeDrawings(\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $pWo
5959
}
6060
}
6161

62+
// unparsed AlternateContent
63+
if (isset($pWorksheet->getParent()->getUnparsedLoadedData()['sheets'][$pWorksheet->getCodeName()]['drawingAlternateContents'])) {
64+
foreach ($pWorksheet->getParent()->getUnparsedLoadedData()['sheets'][$pWorksheet->getCodeName()]['drawingAlternateContents'] as $drawingAlternateContent) {
65+
$objWriter->writeRaw($drawingAlternateContent);
66+
}
67+
}
68+
6269
$objWriter->endElement();
6370

6471
// Return

src/PhpSpreadsheet/Writer/Xlsx/Rels.php

+36-4
Original file line numberDiff line numberDiff line change
@@ -195,18 +195,30 @@ public function writeWorksheetRelationships(\PhpOffice\PhpSpreadsheet\Worksheet\
195195

196196
// Write drawing relationships?
197197
$d = 0;
198+
$drawingOriginalIds = [];
199+
if (isset($pWorksheet->getParent()->getUnparsedLoadedData()['sheets'][$pWorksheet->getCodeName()]['drawingOriginalIds'])) {
200+
$drawingOriginalIds = $pWorksheet->getParent()->getUnparsedLoadedData()['sheets'][$pWorksheet->getCodeName()]['drawingOriginalIds'];
201+
}
202+
198203
if ($includeCharts) {
199204
$charts = $pWorksheet->getChartCollection();
200205
} else {
201206
$charts = [];
202207
}
203-
if (($pWorksheet->getDrawingCollection()->count() > 0) ||
204-
(count($charts) > 0)) {
208+
209+
if (($pWorksheet->getDrawingCollection()->count() > 0) || (count($charts) > 0) || $drawingOriginalIds) {
210+
$relPath = '../drawings/drawing' . $pWorksheetId . '.xml';
211+
$rId = ++$d;
212+
213+
if (isset($drawingOriginalIds[$relPath])) {
214+
$rId = (int) (substr($drawingOriginalIds[$relPath], 3));
215+
}
216+
205217
$this->writeRelationship(
206218
$objWriter,
207-
++$d,
219+
$rId,
208220
'http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing',
209-
'../drawings/drawing' . $pWorksheetId . '.xml'
221+
$relPath
210222
);
211223
}
212224

@@ -255,11 +267,31 @@ public function writeWorksheetRelationships(\PhpOffice\PhpSpreadsheet\Worksheet\
255267
);
256268
}
257269

270+
$this->writeUnparsedRelationship($pWorksheet, $objWriter, 'ctrlProps', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/ctrlProp');
271+
$this->writeUnparsedRelationship($pWorksheet, $objWriter, 'vmlDrawings', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/vmlDrawing');
272+
$this->writeUnparsedRelationship($pWorksheet, $objWriter, 'printerSettings', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/printerSettings');
273+
258274
$objWriter->endElement();
259275

260276
return $objWriter->getData();
261277
}
262278

279+
private function writeUnparsedRelationship(\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $pWorksheet, XMLWriter $objWriter, $relationship, $type)
280+
{
281+
if (!isset($pWorksheet->getParent()->getUnparsedLoadedData()['sheets'][$pWorksheet->getCodeName()][$relationship])) {
282+
return;
283+
}
284+
285+
foreach ($pWorksheet->getParent()->getUnparsedLoadedData()['sheets'][$pWorksheet->getCodeName()][$relationship] as $rId => $value) {
286+
$this->writeRelationship(
287+
$objWriter,
288+
$rId,
289+
$type,
290+
$value['relFilePath']
291+
);
292+
}
293+
}
294+
263295
/**
264296
* Write drawing relationships to XML format.
265297
*

src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php

+41-7
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,12 @@ public function writeWorksheet(PhpspreadsheetWorksheet $pSheet, $pStringTable =
5151
$objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/spreadsheetml/2006/main');
5252
$objWriter->writeAttribute('xmlns:r', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships');
5353

54+
$objWriter->writeAttribute('xmlns:xdr', 'http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing');
55+
$objWriter->writeAttribute('xmlns:x14', 'http://schemas.microsoft.com/office/spreadsheetml/2009/9/main');
56+
$objWriter->writeAttribute('xmlns:mc', 'http://schemas.openxmlformats.org/markup-compatibility/2006');
57+
$objWriter->writeAttribute('mc:Ignorable', 'x14ac');
58+
$objWriter->writeAttribute('xmlns:x14ac', 'http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac');
59+
5460
// sheetPr
5561
$this->writeSheetPr($objWriter, $pSheet);
5662

@@ -114,6 +120,9 @@ public function writeWorksheet(PhpspreadsheetWorksheet $pSheet, $pStringTable =
114120
// LegacyDrawingHF
115121
$this->writeLegacyDrawingHF($objWriter, $pSheet);
116122

123+
// AlternateContent
124+
$this->writeAlternateContent($objWriter, $pSheet);
125+
117126
$objWriter->endElement();
118127

119128
// Return
@@ -283,7 +292,7 @@ private function writeSheetViews(XMLWriter $objWriter, PhpspreadsheetWorksheet $
283292
$objWriter->writeAttribute('pane', $pane);
284293
}
285294
$objWriter->writeAttribute('activeCell', $activeCell);
286-
$objWriter->writeAttribute('sqref', $activeCell);
295+
$objWriter->writeAttribute('sqref', $pSheet->getSelectedCells());
287296
$objWriter->endElement();
288297

289298
$objWriter->endElement();
@@ -843,6 +852,10 @@ private function writePageSetup(XMLWriter $objWriter, PhpspreadsheetWorksheet $p
843852
$objWriter->writeAttribute('useFirstPageNumber', '1');
844853
}
845854

855+
if (isset($pSheet->getParent()->getUnparsedLoadedData()['sheets'][$pSheet->getCodeName()]['pageSetupRelId'])) {
856+
$objWriter->writeAttribute('r:id', $pSheet->getParent()->getUnparsedLoadedData()['sheets'][$pSheet->getCodeName()]['pageSetupRelId']);
857+
}
858+
846859
$objWriter->endElement();
847860
}
848861

@@ -1142,16 +1155,26 @@ private function writeCell(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet
11421155
* @param PhpspreadsheetWorksheet $pSheet Worksheet
11431156
* @param bool $includeCharts Flag indicating if we should include drawing details for charts
11441157
*/
1145-
private function writeDrawings(XMLWriter $objWriter = null, PhpspreadsheetWorksheet $pSheet = null, $includeCharts = false)
1158+
private function writeDrawings(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet, $includeCharts = false)
11461159
{
1160+
$hasUnparsedDrawing = isset($pSheet->getParent()->getUnparsedLoadedData()['sheets'][$pSheet->getCodeName()]['drawingOriginalIds']);
11471161
$chartCount = ($includeCharts) ? $pSheet->getChartCollection()->count() : 0;
1162+
if ($chartCount == 0 && $pSheet->getDrawingCollection()->count() == 0 && !$hasUnparsedDrawing) {
1163+
return;
1164+
}
1165+
11481166
// If sheet contains drawings, add the relationships
1149-
if (($pSheet->getDrawingCollection()->count() > 0) ||
1150-
($chartCount > 0)) {
1151-
$objWriter->startElement('drawing');
1152-
$objWriter->writeAttribute('r:id', 'rId1');
1153-
$objWriter->endElement();
1167+
$objWriter->startElement('drawing');
1168+
1169+
$rId = 'rId1';
1170+
if (isset($pSheet->getParent()->getUnparsedLoadedData()['sheets'][$pSheet->getCodeName()]['drawingOriginalIds'])) {
1171+
$drawingOriginalIds = $pSheet->getParent()->getUnparsedLoadedData()['sheets'][$pSheet->getCodeName()]['drawingOriginalIds'];
1172+
// take first. In future can be overriten
1173+
$rId = reset($drawingOriginalIds);
11541174
}
1175+
1176+
$objWriter->writeAttribute('r:id', $rId);
1177+
$objWriter->endElement();
11551178
}
11561179

11571180
/**
@@ -1185,4 +1208,15 @@ private function writeLegacyDrawingHF(XMLWriter $objWriter, PhpspreadsheetWorksh
11851208
$objWriter->endElement();
11861209
}
11871210
}
1211+
1212+
private function writeAlternateContent(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet)
1213+
{
1214+
if (empty($pSheet->getParent()->getUnparsedLoadedData()['sheets'][$pSheet->getCodeName()]['AlternateContents'])) {
1215+
return;
1216+
}
1217+
1218+
foreach ($pSheet->getParent()->getUnparsedLoadedData()['sheets'][$pSheet->getCodeName()]['AlternateContents'] as $alternateContent) {
1219+
$objWriter->writeRaw($alternateContent);
1220+
}
1221+
}
11881222
}

0 commit comments

Comments
 (0)