1<?php
2
3namespace PhpOffice\PhpSpreadsheet\Reader\Xml;
4
5use PhpOffice\PhpSpreadsheet\Document\Properties as DocumentProperties;
6use PhpOffice\PhpSpreadsheet\Spreadsheet;
7use SimpleXMLElement;
8
9class Properties
10{
11    /**
12     * @var Spreadsheet
13     */
14    protected $spreadsheet;
15
16    public function __construct(Spreadsheet $spreadsheet)
17    {
18        $this->spreadsheet = $spreadsheet;
19    }
20
21    public function readProperties(SimpleXMLElement $xml, array $namespaces): void
22    {
23        $this->readStandardProperties($xml);
24        $this->readCustomProperties($xml, $namespaces);
25    }
26
27    protected function readStandardProperties(SimpleXMLElement $xml): void
28    {
29        if (isset($xml->DocumentProperties[0])) {
30            $docProps = $this->spreadsheet->getProperties();
31
32            foreach ($xml->DocumentProperties[0] as $propertyName => $propertyValue) {
33                $propertyValue = (string) $propertyValue;
34
35                $this->processStandardProperty($docProps, $propertyName, $propertyValue);
36            }
37        }
38    }
39
40    protected function readCustomProperties(SimpleXMLElement $xml, array $namespaces): void
41    {
42        if (isset($xml->CustomDocumentProperties)) {
43            $docProps = $this->spreadsheet->getProperties();
44
45            foreach ($xml->CustomDocumentProperties[0] as $propertyName => $propertyValue) {
46                $propertyAttributes = self::getAttributes($propertyValue, $namespaces['dt']);
47                $propertyName = preg_replace_callback('/_x([0-9a-f]{4})_/i', [$this, 'hex2str'], $propertyName);
48
49                $this->processCustomProperty($docProps, $propertyName, $propertyValue, $propertyAttributes);
50            }
51        }
52    }
53
54    protected function processStandardProperty(
55        DocumentProperties $docProps,
56        string $propertyName,
57        string $stringValue
58    ): void {
59        switch ($propertyName) {
60            case 'Title':
61                $docProps->setTitle($stringValue);
62
63                break;
64            case 'Subject':
65                $docProps->setSubject($stringValue);
66
67                break;
68            case 'Author':
69                $docProps->setCreator($stringValue);
70
71                break;
72            case 'Created':
73                $docProps->setCreated($stringValue);
74
75                break;
76            case 'LastAuthor':
77                $docProps->setLastModifiedBy($stringValue);
78
79                break;
80            case 'LastSaved':
81                $docProps->setModified($stringValue);
82
83                break;
84            case 'Company':
85                $docProps->setCompany($stringValue);
86
87                break;
88            case 'Category':
89                $docProps->setCategory($stringValue);
90
91                break;
92            case 'Manager':
93                $docProps->setManager($stringValue);
94
95                break;
96            case 'Keywords':
97                $docProps->setKeywords($stringValue);
98
99                break;
100            case 'Description':
101                $docProps->setDescription($stringValue);
102
103                break;
104        }
105    }
106
107    protected function processCustomProperty(
108        DocumentProperties $docProps,
109        string $propertyName,
110        ?SimpleXMLElement $propertyValue,
111        SimpleXMLElement $propertyAttributes
112    ): void {
113        $propertyType = DocumentProperties::PROPERTY_TYPE_UNKNOWN;
114
115        switch ((string) $propertyAttributes) {
116            case 'string':
117                $propertyType = DocumentProperties::PROPERTY_TYPE_STRING;
118                $propertyValue = trim((string) $propertyValue);
119
120                break;
121            case 'boolean':
122                $propertyType = DocumentProperties::PROPERTY_TYPE_BOOLEAN;
123                $propertyValue = (bool) $propertyValue;
124
125                break;
126            case 'integer':
127                $propertyType = DocumentProperties::PROPERTY_TYPE_INTEGER;
128                $propertyValue = (int) $propertyValue;
129
130                break;
131            case 'float':
132                $propertyType = DocumentProperties::PROPERTY_TYPE_FLOAT;
133                $propertyValue = (float) $propertyValue;
134
135                break;
136            case 'dateTime.tz':
137                $propertyType = DocumentProperties::PROPERTY_TYPE_DATE;
138                $propertyValue = trim((string) $propertyValue);
139
140                break;
141        }
142
143        $docProps->setCustomProperty($propertyName, $propertyValue, $propertyType);
144    }
145
146    protected function hex2str(array $hex): string
147    {
148        return mb_chr((int) hexdec($hex[1]), 'UTF-8');
149    }
150
151    private static function getAttributes(?SimpleXMLElement $simple, string $node): SimpleXMLElement
152    {
153        return ($simple === null)
154            ? new SimpleXMLElement('<xml></xml>')
155            : ($simple->attributes($node) ?? new SimpleXMLElement('<xml></xml>'));
156    }
157}
158