• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

PHPOffice / PhpSpreadsheet / 30315709222

27 Jul 2026 11:55PM UTC coverage: 97.119% (-0.05%) from 97.169%
30315709222

Pull #4942

github

web-flow
Merge 46f7c051d into 540dfb463
Pull Request #4942: Feature/pivot table read

787 of 836 new or added lines in 12 files covered. (94.14%)

49176 of 50635 relevant lines covered (97.12%)

382.11 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

91.67
/src/PhpSpreadsheet/Reader/Xlsx/PivotTableReader.php
1
<?php
2

3
namespace PhpOffice\PhpSpreadsheet\Reader\Xlsx;
4

5
use PhpOffice\PhpSpreadsheet\Worksheet\PivotTable\PivotCacheDefinition;
6
use PhpOffice\PhpSpreadsheet\Worksheet\PivotTable\PivotField;
7
use PhpOffice\PhpSpreadsheet\Worksheet\PivotTable\PivotTable;
8
use 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(
11✔
28
        Worksheet $worksheet,
29
        SimpleXMLElement $pivotTableXml,
30
        ?SimpleXMLElement $cacheDefinitionXml = null
31
    ) {
32
        $this->worksheet = $worksheet;
11✔
33
        $this->pivotTableXml = $pivotTableXml;
11✔
34
        $this->cacheDefinitionXml = $cacheDefinitionXml;
11✔
35
    }
36

37
    /**
38
     * Parse the pivot table definition and add it to the worksheet.
39
     */
40
    public function load(): void
11✔
41
    {
42
        $attributes = $this->pivotTableXml->attributes();
11✔
43

44
        $pivotTable = new PivotTable((string) ($attributes['name'] ?? ''));
11✔
45

46
        if (isset($this->pivotTableXml->location)) {
11✔
47
            $locationAttributes = $this->pivotTableXml->location->attributes();
11✔
48
            $pivotTable->setLocation((string) ($locationAttributes['ref'] ?? ''));
11✔
49
        }
50

51
        $cacheDefinition = $this->readCacheDefinition(
11✔
52
            isset($attributes['cacheId']) ? (int) $attributes['cacheId'] : null
11✔
53
        );
11✔
54
        $pivotTable->setCacheDefinition($cacheDefinition);
11✔
55

56
        $this->readFields($pivotTable, $cacheDefinition);
11✔
57

58
        $this->worksheet->addPivotTable($pivotTable);
11✔
59
    }
60

61
    /**
62
     * Build the cache definition (source range + field names) if we have it.
63
     */
64
    private function readCacheDefinition(?int $cacheId): PivotCacheDefinition
11✔
65
    {
66
        $cacheDefinition = new PivotCacheDefinition($cacheId);
11✔
67

68
        if ($this->cacheDefinitionXml === null) {
11✔
NEW
69
            return $cacheDefinition;
×
70
        }
71

72
        $source = $this->cacheDefinitionXml->cacheSource;
11✔
73
        if (isset($source->worksheetSource)) {
11✔
74
            $sourceAttributes = $source->worksheetSource->attributes();
11✔
75
            if (isset($sourceAttributes['sheet'])) {
11✔
76
                $cacheDefinition->setSourceWorksheet((string) $sourceAttributes['sheet']);
11✔
77
            }
78
            if (isset($sourceAttributes['ref'])) {
11✔
79
                $cacheDefinition->setSourceRange((string) $sourceAttributes['ref']);
11✔
80
            }
81
        }
82

83
        if (isset($this->cacheDefinitionXml->cacheFields)) {
11✔
84
            foreach ($this->cacheDefinitionXml->cacheFields->cacheField as $cacheField) {
11✔
85
                $fieldAttributes = $cacheField->attributes();
11✔
86
                $cacheDefinition->addCacheField((string) ($fieldAttributes['name'] ?? ''));
11✔
87
            }
88
        }
89

90
        return $cacheDefinition;
11✔
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
11✔
97
    {
98
        if (!isset($this->pivotTableXml->pivotFields)) {
11✔
NEW
99
            return;
×
100
        }
101

102
        $index = 0;
11✔
103
        /** @var PivotField[] $fields */
104
        $fields = [];
11✔
105
        foreach ($this->pivotTableXml->pivotFields->pivotField as $pivotFieldXml) {
11✔
106
            $field = new PivotField($index, (string) ($cacheDefinition->getCacheFieldName($index) ?? ''));
11✔
107

108
            $fieldAttributes = $pivotFieldXml->attributes();
11✔
109
            if (isset($fieldAttributes['axis'])) {
11✔
110
                $field->setAxis((string) $fieldAttributes['axis']);
11✔
111
            }
112

113
            $fields[$index] = $field;
11✔
114
            $pivotTable->addField($field);
11✔
115
            ++$index;
11✔
116
        }
117

118
        $this->markDataFields($fields);
11✔
119
    }
120

121
    /**
122
     * Flag the fields referenced by <dataFields> as value fields, and record
123
     * their aggregation function.
124
     *
125
     * @param PivotField[] $fields keyed by field index
126
     */
127
    private function markDataFields(array $fields): void
11✔
128
    {
129
        if (!isset($this->pivotTableXml->dataFields)) {
11✔
NEW
130
            return;
×
131
        }
132

133
        foreach ($this->pivotTableXml->dataFields->dataField as $dataFieldXml) {
11✔
134
            $dataAttributes = $dataFieldXml->attributes();
11✔
135
            if (!isset($dataAttributes['fld'])) {
11✔
NEW
136
                continue;
×
137
            }
138
            $fieldIndex = (int) $dataAttributes['fld'];
11✔
139
            if (!isset($fields[$fieldIndex])) {
11✔
NEW
140
                continue;
×
141
            }
142
            $fields[$fieldIndex]->setDataField(true);
11✔
143
            // "sum" is the default subtotal when the attribute is absent.
144
            $fields[$fieldIndex]->setSubtotal(
11✔
145
                isset($dataAttributes['subtotal']) ? (string) $dataAttributes['subtotal'] : 'sum'
11✔
146
            );
11✔
147
        }
148
    }
149
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc