| 1 |
<?php |
| 2 |
|
| 3 |
namespace TablePress\PhpOffice\PhpSpreadsheet\Reader\Xlsx; |
| 4 |
|
| 5 |
use TablePress\PhpOffice\PhpSpreadsheet\Worksheet\PivotTable\PivotCacheDefinition; |
| 6 |
use TablePress\PhpOffice\PhpSpreadsheet\Worksheet\PivotTable\PivotField; |
| 7 |
use TablePress\PhpOffice\PhpSpreadsheet\Worksheet\PivotTable\PivotTable; |
| 8 |
use TablePress\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; |
| 9 |
use SimpleXMLElement; |
| 10 |
|
| 11 |
/** |
| 12 |
* Reads a pivot table definition (and its associated cache definition) from |
| 13 |
* the Xlsx parts into the read-only PivotTable object model. |
| 14 |
* |
| 15 |
* This only extracts metadata (name, location, source and field layout); the |
| 16 |
* raw pivot XML parts continue to be preserved verbatim for write-back, so |
| 17 |
* loading a pivot table here never changes what is written out. |
| 18 |
*/ |
| 19 |
class PivotTableReader |
| 20 |
{ |
| 21 |
private Worksheet $worksheet; |
| 22 |
|
| 23 |
private SimpleXMLElement $pivotTableXml; |
| 24 |
|
| 25 |
private ?SimpleXMLElement $cacheDefinitionXml; |
| 26 |
|
| 27 |
public function __construct( |
| 28 |
Worksheet $worksheet, |
| 29 |
SimpleXMLElement $pivotTableXml, |
| 30 |
?SimpleXMLElement $cacheDefinitionXml = null |
| 31 |
) { |
| 32 |
$this->worksheet = $worksheet; |
| 33 |
$this->pivotTableXml = $pivotTableXml; |
| 34 |
$this->cacheDefinitionXml = $cacheDefinitionXml; |
| 35 |
} |
| 36 |
|
| 37 |
/** |
| 38 |
* Parse the pivot table definition and add it to the worksheet. |
| 39 |
*/ |
| 40 |
public function load(): void |
| 41 |
{ |
| 42 |
$attributes = $this->pivotTableXml->attributes(); |
| 43 |
|
| 44 |
$pivotTable = new PivotTable((string) ($attributes['name'] ?? '')); |
| 45 |
|
| 46 |
if (isset($this->pivotTableXml->location)) { |
| 47 |
$locationAttributes = $this->pivotTableXml->location->attributes(); |
| 48 |
$pivotTable->setLocation((string) ($locationAttributes['ref'] ?? '')); |
| 49 |
} |
| 50 |
|
| 51 |
$cacheDefinition = $this->readCacheDefinition( |
| 52 |
isset($attributes['cacheId']) ? (int) $attributes['cacheId'] : null |
| 53 |
); |
| 54 |
$pivotTable->setCacheDefinition($cacheDefinition); |
| 55 |
|
| 56 |
if (isset($this->pivotTableXml->pivotFields)) { |
| 57 |
$this->readFields($pivotTable, $cacheDefinition); |
| 58 |
} |
| 59 |
|
| 60 |
$this->worksheet->addPivotTable($pivotTable); |
| 61 |
} |
| 62 |
|
| 63 |
/** |
| 64 |
* Build the cache definition (source range + field names) if we have it. |
| 65 |
*/ |
| 66 |
private function readCacheDefinition(?int $cacheId): PivotCacheDefinition |
| 67 |
{ |
| 68 |
$cacheDefinition = new PivotCacheDefinition($cacheId); |
| 69 |
|
| 70 |
if ($this->cacheDefinitionXml !== null) { |
| 71 |
$source = $this->cacheDefinitionXml->cacheSource; |
| 72 |
if (isset($source->worksheetSource)) { |
| 73 |
$sourceAttributes = $source->worksheetSource->attributes(); |
| 74 |
if (isset($sourceAttributes['sheet'])) { |
| 75 |
$cacheDefinition->setSourceWorksheet((string) $sourceAttributes['sheet']); |
| 76 |
} |
| 77 |
if (isset($sourceAttributes['ref'])) { |
| 78 |
$cacheDefinition->setSourceRange((string) $sourceAttributes['ref']); |
| 79 |
} |
| 80 |
} |
| 81 |
|
| 82 |
if (isset($this->cacheDefinitionXml->cacheFields)) { |
| 83 |
foreach ($this->cacheDefinitionXml->cacheFields->cacheField as $cacheField) { |
| 84 |
$fieldAttributes = $cacheField->attributes(); |
| 85 |
$cacheDefinition->addCacheField((string) ($fieldAttributes['name'] ?? '')); |
| 86 |
} |
| 87 |
} |
| 88 |
} |
| 89 |
|
| 90 |
return $cacheDefinition; |
| 91 |
} |
| 92 |
|
| 93 |
/** |
| 94 |
* Read the pivotFields and the axis/data placement sections into fields. |
| 95 |
*/ |
| 96 |
private function readFields(PivotTable $pivotTable, PivotCacheDefinition $cacheDefinition): void |
| 97 |
{ |
| 98 |
$index = 0; |
| 99 |
/** @var PivotField[] $fields */ |
| 100 |
$fields = []; |
| 101 |
foreach ($this->pivotTableXml->pivotFields->pivotField as $pivotFieldXml) { |
| 102 |
$field = new PivotField($index, (string) ($cacheDefinition->getCacheFieldName($index) ?? '')); |
| 103 |
|
| 104 |
$fieldAttributes = $pivotFieldXml->attributes(); |
| 105 |
if (isset($fieldAttributes['axis'])) { |
| 106 |
$field->setAxis((string) $fieldAttributes['axis']); |
| 107 |
} |
| 108 |
|
| 109 |
$fields[$index] = $field; |
| 110 |
$pivotTable->addField($field); |
| 111 |
++$index; |
| 112 |
} |
| 113 |
|
| 114 |
$this->markDataFields($fields); |
| 115 |
} |
| 116 |
|
| 117 |
/** |
| 118 |
* Flag the fields referenced by <dataFields> as value fields, and record |
| 119 |
* their aggregation function. |
| 120 |
* |
| 121 |
* @param PivotField[] $fields keyed by field index |
| 122 |
*/ |
| 123 |
private function markDataFields(array $fields): void |
| 124 |
{ |
| 125 |
if (isset($this->pivotTableXml->dataFields)) { |
| 126 |
foreach ($this->pivotTableXml->dataFields->dataField as $dataFieldXml) { |
| 127 |
$dataAttributes = $dataFieldXml->attributes(); |
| 128 |
if (isset($dataAttributes['fld'])) { |
| 129 |
$fieldIndex = (int) $dataAttributes['fld']; |
| 130 |
if (isset($fields[$fieldIndex])) { |
| 131 |
$fields[$fieldIndex]->setDataField(true); |
| 132 |
// "sum" is the default subtotal when the attribute is absent. |
| 133 |
$fields[$fieldIndex]->setSubtotal( |
| 134 |
isset($dataAttributes['subtotal']) ? (string) $dataAttributes['subtotal'] : 'sum' |
| 135 |
); |
| 136 |
} |
| 137 |
} |
| 138 |
} |
| 139 |
} |
| 140 |
} |
| 141 |
} |
| 142 |
|