Failed Conditions
Push — perf-tests ( 50942d...2fc93e )
by Adrien
14:53
created

CellValueFormatter::extractAndFormatNodeValue()   C

Complexity

Conditions 9
Paths 9

Size

Total Lines 24
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 24
rs 5.3563
cc 9
eloc 20
nc 9
nop 1
1
<?php
2
3
namespace Box\Spout\Reader\ODS\Helper;
4
5
/**
6
 * Class CellValueFormatter
7
 * This class provides helper functions to format cell values
8
 *
9
 * @package Box\Spout\Reader\ODS\Helper
10
 */
11
class CellValueFormatter
12
{
13
    /** Definition of all possible cell types */
14
    const CELL_TYPE_STRING = 'string';
15
    const CELL_TYPE_FLOAT = 'float';
16
    const CELL_TYPE_BOOLEAN = 'boolean';
17
    const CELL_TYPE_DATE = 'date';
18
    const CELL_TYPE_TIME = 'time';
19
    const CELL_TYPE_CURRENCY = 'currency';
20
    const CELL_TYPE_PERCENTAGE = 'percentage';
21
    const CELL_TYPE_VOID = 'void';
22
23
    /** Definition of XML nodes names used to parse data */
24
    const XML_NODE_P = 'p';
25
    const XML_NODE_S = 'text:s';
26
    const XML_NODE_A = 'text:a';
27
    const XML_NODE_SPAN = 'text:span';
28
29
    /** Definition of XML attribute used to parse data */
30
    const XML_ATTRIBUTE_TYPE = 'office:value-type';
31
    const XML_ATTRIBUTE_VALUE = 'office:value';
32
    const XML_ATTRIBUTE_BOOLEAN_VALUE = 'office:boolean-value';
33
    const XML_ATTRIBUTE_DATE_VALUE = 'office:date-value';
34
    const XML_ATTRIBUTE_TIME_VALUE = 'office:time-value';
35
    const XML_ATTRIBUTE_CURRENCY = 'office:currency';
36
    const XML_ATTRIBUTE_C = 'text:c';
37
38
    /** @var bool Whether date/time values should be returned as PHP objects or be formatted as strings */
39
    protected $shouldFormatDates;
40
41
    /** @var \Box\Spout\Common\Escaper\ODS Used to unescape XML data */
42
    protected $escaper;
43
44
    /**
45
     * @param bool $shouldFormatDates Whether date/time values should be returned as PHP objects or be formatted as strings
46
     */
47
    public function __construct($shouldFormatDates)
48
    {
49
        $this->shouldFormatDates = $shouldFormatDates;
50
51
        /** @noinspection PhpUnnecessaryFullyQualifiedNameInspection */
52
        $this->escaper = \Box\Spout\Common\Escaper\ODS::getInstance();
53
    }
54
55
    /**
56
     * Returns the (unescaped) correctly marshalled, cell value associated to the given XML node.
57
     * @see http://docs.oasis-open.org/office/v1.2/os/OpenDocument-v1.2-os-part1.html#refTable13
58
     *
59
     * @param \DOMNode $node
60
     * @return string|int|float|bool|\DateTime|\DateInterval|null The value associated with the cell, empty string if cell's type is void/undefined, null on error
61
     */
62
    public function extractAndFormatNodeValue($node)
63
    {
64
        $cellType = $node->getAttribute(self::XML_ATTRIBUTE_TYPE);
65
66
        switch ($cellType) {
67
            case self::CELL_TYPE_STRING:
68
                return $this->formatStringCellValue($node);
69
            case self::CELL_TYPE_FLOAT:
70
                return $this->formatFloatCellValue($node);
71
            case self::CELL_TYPE_BOOLEAN:
72
                return $this->formatBooleanCellValue($node);
73
            case self::CELL_TYPE_DATE:
74
                return $this->formatDateCellValue($node);
75
            case self::CELL_TYPE_TIME:
76
                return $this->formatTimeCellValue($node);
77
            case self::CELL_TYPE_CURRENCY:
78
                return $this->formatCurrencyCellValue($node);
79
            case self::CELL_TYPE_PERCENTAGE:
80
                return $this->formatPercentageCellValue($node);
81
            case self::CELL_TYPE_VOID:
82
            default:
83
                return '';
84
        }
85
    }
86
87
    /**
88
     * Returns the cell String value.
89
     *
90
     * @param \DOMNode $node
91
     * @return string The value associated with the cell
92
     */
93
    protected function formatStringCellValue($node)
94
    {
95
        $pNodeValues = [];
96
        $pNodes = $node->getElementsByTagName(self::XML_NODE_P);
97
98
        foreach ($pNodes as $pNode) {
99
            $currentPValue = '';
100
101
            foreach ($pNode->childNodes as $childNode) {
102
                if ($childNode instanceof \DOMText) {
103
                    $currentPValue .= $childNode->nodeValue;
104
                } else if ($childNode->nodeName === self::XML_NODE_S) {
105
                    $spaceAttribute = $childNode->getAttribute(self::XML_ATTRIBUTE_C);
106
                    $numSpaces = (!empty($spaceAttribute)) ? intval($spaceAttribute) : 1;
107
                    $currentPValue .= str_repeat(' ', $numSpaces);
108
                } else if ($childNode->nodeName === self::XML_NODE_A || $childNode->nodeName === self::XML_NODE_SPAN) {
109
                    $currentPValue .= $childNode->nodeValue;
110
                }
111
            }
112
113
            $pNodeValues[] = $currentPValue;
114
        }
115
116
        $escapedCellValue = implode("\n", $pNodeValues);
117
        $cellValue = $this->escaper->unescape($escapedCellValue);
118
        return $cellValue;
119
    }
120
121
    /**
122
     * Returns the cell Numeric value from the given node.
123
     *
124
     * @param \DOMNode $node
125
     * @return int|float The value associated with the cell
126
     */
127
    protected function formatFloatCellValue($node)
128
    {
129
        $nodeValue = $node->getAttribute(self::XML_ATTRIBUTE_VALUE);
130
        $nodeIntValue = intval($nodeValue);
131
        // The "==" is intentionally not a "===" because only the value matters, not the type
132
        $cellValue = ($nodeIntValue == $nodeValue) ? $nodeIntValue : floatval($nodeValue);
133
        return $cellValue;
134
    }
135
136
    /**
137
     * Returns the cell Boolean value from the given node.
138
     *
139
     * @param \DOMNode $node
140
     * @return bool The value associated with the cell
141
     */
142
    protected function formatBooleanCellValue($node)
143
    {
144
        $nodeValue = $node->getAttribute(self::XML_ATTRIBUTE_BOOLEAN_VALUE);
145
        // !! is similar to boolval()
146
        $cellValue = !!$nodeValue;
147
        return $cellValue;
148
    }
149
150
    /**
151
     * Returns the cell Date value from the given node.
152
     *
153
     * @param \DOMNode $node
154
     * @return \DateTime|string|null The value associated with the cell or NULL if invalid date value
155
     */
156
    protected function formatDateCellValue($node)
157
    {
158
        // The XML node looks like this:
159
        // <table:table-cell calcext:value-type="date" office:date-value="2016-05-19T16:39:00" office:value-type="date">
160
        //   <text:p>05/19/16 04:39 PM</text:p>
161
        // </table:table-cell>
162
163
        if ($this->shouldFormatDates) {
164
            // The date is already formatted in the "p" tag
165
            $nodeWithValueAlreadyFormatted = $node->getElementsByTagName(self::XML_NODE_P)->item(0);
166
            return $nodeWithValueAlreadyFormatted->nodeValue;
167
        } else {
168
            // otherwise, get it from the "date-value" attribute
169
            try {
170
                $nodeValue = $node->getAttribute(self::XML_ATTRIBUTE_DATE_VALUE);
171
                return new \DateTime($nodeValue);
172
            } catch (\Exception $e) {
173
                return null;
174
            }
175
        }
176
    }
177
178
    /**
179
     * Returns the cell Time value from the given node.
180
     *
181
     * @param \DOMNode $node
182
     * @return \DateInterval|string|null The value associated with the cell or NULL if invalid time value
183
     */
184
    protected function formatTimeCellValue($node)
185
    {
186
        // The XML node looks like this:
187
        // <table:table-cell calcext:value-type="time" office:time-value="PT13H24M00S" office:value-type="time">
188
        //   <text:p>01:24:00 PM</text:p>
189
        // </table:table-cell>
190
191
        if ($this->shouldFormatDates) {
192
            // The date is already formatted in the "p" tag
193
            $nodeWithValueAlreadyFormatted = $node->getElementsByTagName(self::XML_NODE_P)->item(0);
194
            return $nodeWithValueAlreadyFormatted->nodeValue;
195
        } else {
196
            // otherwise, get it from the "time-value" attribute
197
            try {
198
                $nodeValue = $node->getAttribute(self::XML_ATTRIBUTE_TIME_VALUE);
199
                return new \DateInterval($nodeValue);
200
            } catch (\Exception $e) {
201
                return null;
202
            }
203
        }
204
    }
205
206
    /**
207
     * Returns the cell Currency value from the given node.
208
     *
209
     * @param \DOMNode $node
210
     * @return string The value associated with the cell (e.g. "100 USD" or "9.99 EUR")
211
     */
212
    protected function formatCurrencyCellValue($node)
213
    {
214
        $value = $node->getAttribute(self::XML_ATTRIBUTE_VALUE);
215
        $currency = $node->getAttribute(self::XML_ATTRIBUTE_CURRENCY);
216
217
        return "$value $currency";
218
    }
219
220
    /**
221
     * Returns the cell Percentage value from the given node.
222
     *
223
     * @param \DOMNode $node
224
     * @return int|float The value associated with the cell
225
     */
226
    protected function formatPercentageCellValue($node)
227
    {
228
        // percentages are formatted like floats
229
        return $this->formatFloatCellValue($node);
230
    }
231
}
232