Completed
Pull Request — develop_3.0 (#427)
by Adrien
02:28
created

WorksheetManager::addRow()   B

Complexity

Conditions 5
Paths 6

Size

Total Lines 41
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 21
CRAP Score 5.0023

Importance

Changes 0
Metric Value
dl 0
loc 41
ccs 21
cts 22
cp 0.9545
rs 8.439
c 0
b 0
f 0
cc 5
eloc 21
nc 6
nop 3
crap 5.0023
1
<?php
2
3
namespace Box\Spout\Writer\ODS\Manager;
4
5
use Box\Spout\Common\Exception\InvalidArgumentException;
6
use Box\Spout\Common\Exception\IOException;
7
use Box\Spout\Common\Helper\StringHelper;
8
use Box\Spout\Writer\Common\Cell;
9
use Box\Spout\Writer\Entity\Worksheet;
10
use Box\Spout\Writer\Manager\WorksheetManagerInterface;
11
use Box\Spout\Writer\ODS\Helper\StyleHelper;
12
use Box\Spout\Writer\Style\Style;
13
14
/**
15
 * Class WorksheetManager
16
 * ODS worksheet manager, providing the interfaces to work with ODS worksheets.
17
 *
18
 * @package Box\Spout\Writer\ODS\Manager
19
 */
20
class WorksheetManager implements WorksheetManagerInterface
21
{
22
    /** @var StyleHelper Helper to work with styles */
23
    private $styleHelper;
24
25
    /** @var \Box\Spout\Common\Escaper\ODS Strings escaper */
26
    private $stringsEscaper;
27
28
    /** @var StringHelper String helper */
29
    private $stringHelper;
30
31
    /**
32
     * WorksheetManager constructor.
33
     *
34
     * @param StyleHelper $styleHelper
35
     * @param \Box\Spout\Common\Escaper\ODS $stringsEscaper
36
     * @param StringHelper $stringHelper
37
     */
38 43
    public function __construct(
39
        StyleHelper $styleHelper,
40
        \Box\Spout\Common\Escaper\ODS $stringsEscaper,
41
        StringHelper $stringHelper)
42
    {
43 43
        $this->styleHelper = $styleHelper;
44 43
        $this->stringsEscaper = $stringsEscaper;
45 43
        $this->stringHelper = $stringHelper;
46 43
    }
47
48
    /**
49
     * Prepares the worksheet to accept data
50
     *
51
     * @param Worksheet $worksheet The worksheet to start
52
     * @return void
53
     * @throws \Box\Spout\Common\Exception\IOException If the sheet data file cannot be opened for writing
54
     */
55 43
    public function startSheet(Worksheet $worksheet)
56
    {
57 43
        $sheetFilePointer = fopen($worksheet->getFilePath(), 'w');
58 43
        $this->throwIfSheetFilePointerIsNotAvailable($sheetFilePointer);
59
60 43
        $worksheet->setFilePointer($sheetFilePointer);
61 43
    }
62
63
    /**
64
     * Checks if the sheet has been sucessfully created. Throws an exception if not.
65
     *
66
     * @param bool|resource $sheetFilePointer Pointer to the sheet data file or FALSE if unable to open the file
67
     * @return void
68
     * @throws IOException If the sheet data file cannot be opened for writing
69
     */
70 43
    private function throwIfSheetFilePointerIsNotAvailable($sheetFilePointer)
71
    {
72 43
        if (!$sheetFilePointer) {
73
            throw new IOException('Unable to open sheet for writing.');
74
        }
75 43
    }
76
77
    /**
78
     * Returns the table XML root node as string.
79
     *
80
     * @param Worksheet $worksheet
81
     * @return string <table> node as string
82
     */
83 34
    public function getTableElementStartAsString(Worksheet $worksheet)
84
    {
85 34
        $externalSheet = $worksheet->getExternalSheet();
86 34
        $escapedSheetName = $this->stringsEscaper->escape($externalSheet->getName());
87 34
        $tableStyleName = 'ta' . ($externalSheet->getIndex() + 1);
88
89 34
        $tableElement  = '<table:table table:style-name="' . $tableStyleName . '" table:name="' . $escapedSheetName . '">';
90 34
        $tableElement .= '<table:table-column table:default-cell-style-name="ce1" table:style-name="co1" table:number-columns-repeated="' . $worksheet->getMaxNumColumns() . '"/>';
91
92 34
        return $tableElement;
93
    }
94
95
    /**
96
     * Adds data to the given worksheet.
97
     *
98
     * @param Worksheet $worksheet The worksheet to add the row to
99
     * @param array $dataRow Array containing data to be written. Cannot be empty.
100
     *          Example $dataRow = ['data1', 1234, null, '', 'data5'];
101
     * @param Style $rowStyle Style to be applied to the row. NULL means use default style.
102
     * @return void
103
     * @throws IOException If the data cannot be written
104
     * @throws InvalidArgumentException If a cell value's type is not supported
105
     */
106 31
    public function addRow(Worksheet $worksheet, $dataRow, $rowStyle)
107
    {
108
        // $dataRow can be an associative array. We need to transform
109
        // it into a regular array, as we'll use the numeric indexes.
110 31
        $dataRowWithNumericIndexes = array_values($dataRow);
111
112 31
        $styleIndex = ($rowStyle->getId() + 1); // 1-based
113 31
        $cellsCount = count($dataRow);
114
115 31
        $data = '<table:table-row table:style-name="ro1">';
116
117 31
        $currentCellIndex = 0;
118 31
        $nextCellIndex = 1;
119
120 31
        for ($i = 0; $i < $cellsCount; $i++) {
121 31
            $currentCellValue = $dataRowWithNumericIndexes[$currentCellIndex];
122
123
            // Using isset here because it is way faster than array_key_exists...
124 31
            if (!isset($dataRowWithNumericIndexes[$nextCellIndex]) ||
125 21
                $currentCellValue !== $dataRowWithNumericIndexes[$nextCellIndex]) {
126
127 31
                $numTimesValueRepeated = ($nextCellIndex - $currentCellIndex);
128 31
                $data .= $this->getCellXML($currentCellValue, $styleIndex, $numTimesValueRepeated);
129
130 30
                $currentCellIndex = $nextCellIndex;
131
            }
132
133 30
            $nextCellIndex++;
134
        }
135
136 30
        $data .= '</table:table-row>';
137
138 30
        $wasWriteSuccessful = fwrite($worksheet->getFilePointer(), $data);
139 30
        if ($wasWriteSuccessful === false) {
140
            throw new IOException("Unable to write data in {$worksheet->getFilePath()}");
141
        }
142
143
        // only update the count if the write worked
144 30
        $lastWrittenRowIndex = $worksheet->getLastWrittenRowIndex();
145 30
        $worksheet->setLastWrittenRowIndex($lastWrittenRowIndex + 1);
146 30
    }
147
148
    /**
149
     * Returns the cell XML content, given its value.
150
     *
151
     * @param mixed $cellValue The value to be written
152
     * @param int $styleIndex Index of the used style
153
     * @param int $numTimesValueRepeated Number of times the value is consecutively repeated
154
     * @return string The cell XML content
155
     * @throws \Box\Spout\Common\Exception\InvalidArgumentException If a cell value's type is not supported
156
     */
157 31
    private function getCellXML($cellValue, $styleIndex, $numTimesValueRepeated)
158
    {
159 31
        $data = '<table:table-cell table:style-name="ce' . $styleIndex . '"';
160
161 31
        if ($numTimesValueRepeated !== 1) {
162 4
            $data .= ' table:number-columns-repeated="' . $numTimesValueRepeated . '"';
163
        }
164
165
        /** @TODO Remove code duplication with XLSX writer: https://github.com/box/spout/pull/383#discussion_r113292746 */
166 31
        if ($cellValue instanceof Cell) {
167 2
            $cell = $cellValue;
168
        } else {
169 29
            $cell = new Cell($cellValue);
170
        }
171
172 31
        if ($cell->isString()) {
173 27
            $data .= ' office:value-type="string" calcext:value-type="string">';
174
175 27
            $cellValueLines = explode("\n", $cell->getValue());
176 27
            foreach ($cellValueLines as $cellValueLine) {
177 27
                $data .= '<text:p>' . $this->stringsEscaper->escape($cellValueLine) . '</text:p>';
178
            }
179
180 27
            $data .= '</table:table-cell>';
181 7
        } else if ($cell->isBoolean()) {
182 3
            $data .= ' office:value-type="boolean" calcext:value-type="boolean" office:boolean-value="' . $cell->getValue() . '">';
183 3
            $data .= '<text:p>' . $cell->getValue() . '</text:p>';
184 3
            $data .= '</table:table-cell>';
185 6
        } else if ($cell->isNumeric()) {
186 3
            $data .= ' office:value-type="float" calcext:value-type="float" office:value="' . $cell->getValue() . '">';
187 3
            $data .= '<text:p>' . $cell->getValue() . '</text:p>';
188 3
            $data .= '</table:table-cell>';
189 4
        } else if ($cell->isEmpty()) {
190 2
            $data .= '/>';
191
        } else {
192 2
            throw new InvalidArgumentException('Trying to add a value with an unsupported type: ' . gettype($cell->getValue()));
193
        }
194
195 30
        return $data;
196
    }
197
198
    /**
199
     * Closes the worksheet
200
     *
201
     * @param Worksheet $worksheet
202
     * @return void
203
     */
204 34
    public function close(Worksheet $worksheet)
205
    {
206 34
        $worksheetFilePointer = $worksheet->getFilePointer();
207
208 34
        if (!is_resource($worksheetFilePointer)) {
209
            return;
210
        }
211
212 34
        fclose($worksheetFilePointer);
213
    }
214
}