Skip to content
This repository was archived by the owner on May 26, 2022. It is now read-only.

Commit 6124528

Browse files
committed
Merge pull request #113 from box/all_ods_cell_types
Support all ODS cell types
2 parents aa79781 + 818ec24 commit 6124528

File tree

8 files changed

+112
-34
lines changed

8 files changed

+112
-34
lines changed

src/Spout/Reader/ODS/Helper/CellValueFormatter.php

Lines changed: 92 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,25 @@ class CellValueFormatter
1212
{
1313
/** Definition of all possible cell types */
1414
const CELL_TYPE_STRING = 'string';
15-
const CELL_TYPE_BOOLEAN = 'boolean';
1615
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';
1722

1823
/** Definition of XML nodes names used to parse data */
1924
const XML_NODE_P = 'p';
2025
const XML_NODE_S = 'text:s';
2126

2227
/** Definition of XML attribute used to parse data */
2328
const XML_ATTRIBUTE_TYPE = 'office:value-type';
29+
const XML_ATTRIBUTE_VALUE = 'office:value';
30+
const XML_ATTRIBUTE_BOOLEAN_VALUE = 'office:boolean-value';
31+
const XML_ATTRIBUTE_DATE_VALUE = 'office:date-value';
32+
const XML_ATTRIBUTE_TIME_VALUE = 'office:time-value';
33+
const XML_ATTRIBUTE_CURRENCY = 'office:currency';
2434
const XML_ATTRIBUTE_C = 'text:c';
2535

2636
/** @var \Box\Spout\Common\Escaper\ODS Used to unescape XML data */
@@ -38,45 +48,36 @@ public function __construct()
3848
/**
3949
* Returns the (unescaped) correctly marshalled, cell value associated to the given XML node.
4050
* @TODO Add other types !!
51+
* @see http://docs.oasis-open.org/office/v1.2/os/OpenDocument-v1.2-os-part1.html#refTable13
4152
*
4253
* @param \DOMNode $node
43-
* @return string|int|float|bool The value associated with the cell (or empty string if cell's type is undefined)
54+
* @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
4455
*/
4556
public function extractAndFormatNodeValue($node)
4657
{
4758
$cellType = $node->getAttribute(self::XML_ATTRIBUTE_TYPE);
48-
$pNodeValue = $this->getFirstPNodeValue($node);
4959

5060
switch ($cellType) {
5161
case self::CELL_TYPE_STRING:
5262
return $this->formatStringCellValue($node);
5363
case self::CELL_TYPE_FLOAT:
54-
return $this->formatFloatCellValue($pNodeValue);
64+
return $this->formatFloatCellValue($node);
5565
case self::CELL_TYPE_BOOLEAN:
56-
return $this->formatBooleanCellValue($pNodeValue);
66+
return $this->formatBooleanCellValue($node);
67+
case self::CELL_TYPE_DATE:
68+
return $this->formatDateCellValue($node);
69+
case self::CELL_TYPE_TIME:
70+
return $this->formatTimeCellValue($node);
71+
case self::CELL_TYPE_CURRENCY:
72+
return $this->formatCurrencyCellValue($node);
73+
case self::CELL_TYPE_PERCENTAGE:
74+
return $this->formatPercentageCellValue($node);
75+
case self::CELL_TYPE_VOID:
5776
default:
5877
return '';
5978
}
6079
}
6180

62-
/**
63-
* Returns the value of the first "<text:p>" node within the given node.
64-
*
65-
* @param \DOMNode $node
66-
* @return string Value for the first "<text:p>" node or empty string if no "<text:p>" found
67-
*/
68-
protected function getFirstPNodeValue($node)
69-
{
70-
$nodeValue = '';
71-
$pNodes = $node->getElementsByTagName(self::XML_NODE_P);
72-
73-
if ($pNodes->length > 0) {
74-
$nodeValue = $pNodes->item(0)->nodeValue;
75-
}
76-
77-
return $nodeValue;
78-
}
79-
8081
/**
8182
* Returns the cell String value.
8283
*
@@ -110,27 +111,87 @@ protected function formatStringCellValue($node)
110111
}
111112

112113
/**
113-
* Returns the cell Numeric value from string of nodeValue.
114+
* Returns the cell Numeric value from the given node.
114115
*
115-
* @param string $pNodeValue
116+
* @param \DOMNode $node
116117
* @return int|float The value associated with the cell
117118
*/
118-
protected function formatFloatCellValue($pNodeValue)
119+
protected function formatFloatCellValue($node)
119120
{
120-
$cellValue = is_int($pNodeValue) ? intval($pNodeValue) : floatval($pNodeValue);
121+
$nodeValue = $node->getAttribute(self::XML_ATTRIBUTE_VALUE);
122+
$cellValue = is_int($nodeValue) ? intval($nodeValue) : floatval($nodeValue);
121123
return $cellValue;
122124
}
123125

124126
/**
125-
* Returns the cell Boolean value from a specific node's Value.
127+
* Returns the cell Boolean value from the given node.
126128
*
127-
* @param string $pNodeValue
129+
* @param \DOMNode $node
128130
* @return bool The value associated with the cell
129131
*/
130-
protected function formatBooleanCellValue($pNodeValue)
132+
protected function formatBooleanCellValue($node)
131133
{
134+
$nodeValue = $node->getAttribute(self::XML_ATTRIBUTE_BOOLEAN_VALUE);
132135
// !! is similar to boolval()
133-
$cellValue = !!$pNodeValue;
136+
$cellValue = !!$nodeValue;
134137
return $cellValue;
135138
}
139+
140+
/**
141+
* Returns the cell Date value from the given node.
142+
*
143+
* @param \DOMNode $node
144+
* @return \DateTime|null The value associated with the cell or NULL if invalid date value
145+
*/
146+
protected function formatDateCellValue($node)
147+
{
148+
try {
149+
$nodeValue = $node->getAttribute(self::XML_ATTRIBUTE_DATE_VALUE);
150+
return new \DateTime($nodeValue);
151+
} catch (\Exception $e) {
152+
return null;
153+
}
154+
}
155+
156+
/**
157+
* Returns the cell Time value from the given node.
158+
*
159+
* @param \DOMNode $node
160+
* @return \DateInterval|null The value associated with the cell or NULL if invalid time value
161+
*/
162+
protected function formatTimeCellValue($node)
163+
{
164+
try {
165+
$nodeValue = $node->getAttribute(self::XML_ATTRIBUTE_TIME_VALUE);
166+
return new \DateInterval($nodeValue);
167+
} catch (\Exception $e) {
168+
return null;
169+
}
170+
}
171+
172+
/**
173+
* Returns the cell Currency value from the given node.
174+
*
175+
* @param \DOMNode $node
176+
* @return string The value associated with the cell (e.g. "100 USD" or "9.99 EUR")
177+
*/
178+
protected function formatCurrencyCellValue($node)
179+
{
180+
$value = $node->getAttribute(self::XML_ATTRIBUTE_VALUE);
181+
$currency = $node->getAttribute(self::XML_ATTRIBUTE_CURRENCY);
182+
183+
return "$value $currency";
184+
}
185+
186+
/**
187+
* Returns the cell Percentage value from the given node.
188+
*
189+
* @param \DOMNode $node
190+
* @return int|float The value associated with the cell
191+
*/
192+
protected function formatPercentageCellValue($node)
193+
{
194+
// percentages are formatted like floats
195+
return $this->formatFloatCellValue($node);
196+
}
136197
}

src/Spout/Reader/ODS/RowIterator.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ protected function getNumColumnsRepeatedForCurrentNode()
174174
* Returns the (unescaped) correctly marshalled, cell value associated to the given XML node.
175175
*
176176
* @param \DOMNode $node
177-
* @return string|int|float|bool The value associated with the cell (or empty string if cell's type is undefined)
177+
* @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
178178
*/
179179
protected function getCellValue($node)
180180
{

src/Spout/Reader/XLSX/Helper/CellValueFormatter.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ protected function formatBooleanCellValue($nodeValue)
165165
* Returns a cell's PHP Date value, associated to the given stored nodeValue.
166166
*
167167
* @param string $nodeValue
168-
* @return \DateTime|null The value associated with the cell (null when the cell has an error)
168+
* @return \DateTime|null The value associated with the cell or NULL if invalid date value
169169
*/
170170
protected function formatDateCellValue($nodeValue)
171171
{

src/Spout/Writer/ODS/Internal/Worksheet.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ protected function getCellContent($cellValue, $styleIndex, $numTimesValueRepeate
194194

195195
$data .= '</table:table-cell>';
196196
} else if (CellHelper::isBoolean($cellValue)) {
197-
$data .= ' office:value-type="boolean" calcext:value-type="boolean" office:value="' . $cellValue . '">';
197+
$data .= ' office:value-type="boolean" calcext:value-type="boolean" office:boolean-value="' . $cellValue . '">';
198198
$data .= '<text:p>' . $cellValue . '</text:p>';
199199
$data .= '</table:table-cell>';
200200
} else if (CellHelper::isNumeric($cellValue)) {

tests/Spout/Reader/ODS/ReaderTest.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,13 +143,21 @@ public function testReadWithFilesGeneratedByExternalSoftwares($fileName, $skipLa
143143
*/
144144
public function testReadShouldSupportAllCellTypes()
145145
{
146+
$utcTz = new \DateTimeZone('UTC');
147+
$honoluluTz = new \DateTimeZone('Pacific/Honolulu'); // UTC-10
148+
146149
$allRows = $this->getAllRowsForFile('sheet_with_all_cell_types.ods');
147150

148151
$expectedRows = [
149152
[
150153
'ods--11', 'ods--12',
151154
true, false,
152155
0, 10.43,
156+
new \DateTime('1987-11-29T00:00:00', $utcTz), new \DateTime('1987-11-29T13:37:00', $utcTz),
157+
new \DateTime('1987-11-29T13:37:00', $utcTz), new \DateTime('1987-11-29T13:37:00', $honoluluTz),
158+
new \DateInterval('PT13H37M00S'),
159+
0, 0.42,
160+
'42 USD', '9.99 EUR',
153161
'',
154162
],
155163
];
@@ -165,6 +173,15 @@ public function testReadShouldReturnEmptyStringOnUndefinedCellType()
165173
$this->assertEquals([['ods--11', '', 'ods--13']], $allRows);
166174
}
167175

176+
/**
177+
* @return void
178+
*/
179+
public function testReadShouldReturnNullOnInvalidDateOrTime()
180+
{
181+
$allRows = $this->getAllRowsForFile('sheet_with_invalid_date_time.ods');
182+
$this->assertEquals([[null, null]], $allRows);
183+
}
184+
168185
/**
169186
* @return void
170187
*/
180 Bytes
Binary file not shown.
2.52 KB
Binary file not shown.
7 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)